mirror of
https://github.com/RoboSats/robosats.git
synced 2025-07-18 16:53:16 +00:00
Merge branch 'main' into the-federation-layer-v0.6.0
This commit is contained in:
@ -108,7 +108,7 @@ MAX_PUBLIC_ORDERS = 100
|
|||||||
# Minimum order size (must be bigger than DB constrain in /robosats/settings.py MIN_TRADE, currently 20_000 Sats)
|
# Minimum order size (must be bigger than DB constrain in /robosats/settings.py MIN_TRADE, currently 20_000 Sats)
|
||||||
MIN_ORDER_SIZE = 20000
|
MIN_ORDER_SIZE = 20000
|
||||||
# Minimum order size (must be smaller than DB constrain in /robosats/settings.py MAX_TRADE, currently 5_000_000 Sats)
|
# Minimum order size (must be smaller than DB constrain in /robosats/settings.py MAX_TRADE, currently 5_000_000 Sats)
|
||||||
MAX_ORDER_SIZE = 5000000
|
MAX_ORDER_SIZE = 500000
|
||||||
|
|
||||||
# For CLTV_expiry calculation
|
# For CLTV_expiry calculation
|
||||||
# Assume 8 min/block assumed
|
# Assume 8 min/block assumed
|
||||||
|
10
.github/workflows/integration-tests.yml
vendored
10
.github/workflows/integration-tests.yml
vendored
@ -20,9 +20,9 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
max-parallel: 2
|
max-parallel: 2
|
||||||
matrix:
|
matrix:
|
||||||
python-tag: ['3.11.6-slim-bookworm', '3.12-slim-bookworm']
|
python-tag: ['3.11.6-slim-bookworm', '3.12.1-slim-bookworm']
|
||||||
lnd-version: ['v0.17.0-beta', 'v0.17.2-beta.rc1']
|
lnd-version: ['v0.17.3-beta']
|
||||||
cln-version: ['v23.08.1']
|
cln-version: ['v23.11.2']
|
||||||
ln-vendor: ['LND'] #, 'CLN']
|
ln-vendor: ['LND'] #, 'CLN']
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@ -37,7 +37,7 @@ jobs:
|
|||||||
- uses: satackey/action-docker-layer-caching@v0.0.11
|
- uses: satackey/action-docker-layer-caching@v0.0.11
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
key: coordinator-docker-cache-${{ hashFiles('Dockerfile', 'requirements.txt', 'requirements_dev.txt') }}
|
key: coordinator-docker-cache-${{ hashFiles('Dockerfile', 'requirements.txt', 'requirements_dev.txt') }}-${{ matrix.ln-vendor }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
coordinator-docker-cache-
|
coordinator-docker-cache-
|
||||||
|
|
||||||
@ -79,5 +79,5 @@ jobs:
|
|||||||
- name: 'Upload coverage report'
|
- name: 'Upload coverage report'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: coverage-report-${{ matrix.python-tag }}-${{ matrix.ln-vendor }}
|
name: coverage-report-${{ matrix.python-tag }}-${{ matrix.ln-vendor }}-${{ github.run_id }}
|
||||||
path: htmlcov/
|
path: htmlcov/
|
@ -1,6 +1,3 @@
|
|||||||
# TODO
|
|
||||||
# add a hook which automatically generates the OpenApi schema on API changes
|
|
||||||
# and places them in an appropriate location
|
|
||||||
exclude: '(api|chat|control)/migrations/.*'
|
exclude: '(api|chat|control)/migrations/.*'
|
||||||
repos:
|
repos:
|
||||||
- repo: 'https://github.com/pre-commit/pre-commit-hooks'
|
- repo: 'https://github.com/pre-commit/pre-commit-hooks'
|
||||||
@ -50,27 +47,20 @@ repos:
|
|||||||
files: ^mobile/
|
files: ^mobile/
|
||||||
types_or: [javascript, jsx, ts, tsx, css, markdown, json] # uses https://github.com/pre-commit/identify
|
types_or: [javascript, jsx, ts, tsx, css, markdown, json] # uses https://github.com/pre-commit/identify
|
||||||
entry: bash -c 'cd mobile && npm run format'
|
entry: bash -c 'cd mobile && npm run format'
|
||||||
- id: isort
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
name: isort
|
rev: v0.1.11
|
||||||
stages:
|
hooks:
|
||||||
- commit
|
- id: ruff
|
||||||
- merge-commit
|
stages:
|
||||||
language: system
|
- commit
|
||||||
types: [python]
|
- merge-commit
|
||||||
entry: isort
|
language: system
|
||||||
- id: black
|
args: [ --fix ]
|
||||||
name: black
|
types: [python]
|
||||||
stages:
|
- id: ruff-format
|
||||||
- commit
|
stages:
|
||||||
- merge-commit
|
- commit
|
||||||
language: system
|
- merge-commit
|
||||||
types: [python]
|
language: system
|
||||||
entry: black
|
types: [python]
|
||||||
- id: flake8
|
|
||||||
name: flake8
|
|
||||||
stages:
|
|
||||||
- commit
|
|
||||||
- merge-commit
|
|
||||||
language: system
|
|
||||||
types: [python]
|
|
||||||
entry: flake8
|
|
||||||
|
@ -9,7 +9,7 @@ from django.utils import timezone
|
|||||||
from api.lightning.node import LNNode
|
from api.lightning.node import LNNode
|
||||||
from api.models import Currency, LNPayment, MarketTick, OnchainPayment, Order
|
from api.models import Currency, LNPayment, MarketTick, OnchainPayment, Order
|
||||||
from api.tasks import send_devfund_donation, send_notification
|
from api.tasks import send_devfund_donation, send_notification
|
||||||
from api.utils import validate_onchain_address
|
from api.utils import get_minning_fee, validate_onchain_address
|
||||||
from chat.models import Message
|
from chat.models import Message
|
||||||
|
|
||||||
FEE = float(config("FEE"))
|
FEE = float(config("FEE"))
|
||||||
@ -19,7 +19,7 @@ ESCROW_USERNAME = config("ESCROW_USERNAME")
|
|||||||
PENALTY_TIMEOUT = int(config("PENALTY_TIMEOUT"))
|
PENALTY_TIMEOUT = int(config("PENALTY_TIMEOUT"))
|
||||||
|
|
||||||
MIN_ORDER_SIZE = config("MIN_ORDER_SIZE", cast=int, default=20_000)
|
MIN_ORDER_SIZE = config("MIN_ORDER_SIZE", cast=int, default=20_000)
|
||||||
MAX_ORDER_SIZE = config("MAX_ORDER_SIZE", cast=int, default=5_000_000)
|
MAX_ORDER_SIZE = config("MAX_ORDER_SIZE", cast=int, default=500_000)
|
||||||
|
|
||||||
EXP_MAKER_BOND_INVOICE = int(config("EXP_MAKER_BOND_INVOICE"))
|
EXP_MAKER_BOND_INVOICE = int(config("EXP_MAKER_BOND_INVOICE"))
|
||||||
EXP_TAKER_BOND_INVOICE = int(config("EXP_TAKER_BOND_INVOICE"))
|
EXP_TAKER_BOND_INVOICE = int(config("EXP_TAKER_BOND_INVOICE"))
|
||||||
@ -640,10 +640,7 @@ class Logics:
|
|||||||
): # Not enough onchain balance to commit for this swap.
|
): # Not enough onchain balance to commit for this swap.
|
||||||
return False
|
return False
|
||||||
|
|
||||||
suggested_mining_fee_rate = LNNode.estimate_fee(
|
suggested_mining_fee_rate = get_minning_fee("suggested", preliminary_amount)
|
||||||
amount_sats=preliminary_amount,
|
|
||||||
target_conf=config("SUGGESTED_TARGET_CONF", cast=int, default=2),
|
|
||||||
)["mining_fee_rate"]
|
|
||||||
|
|
||||||
# Hardcap mining fee suggested at 1000 sats/vbyte
|
# Hardcap mining fee suggested at 1000 sats/vbyte
|
||||||
if suggested_mining_fee_rate > 1000:
|
if suggested_mining_fee_rate > 1000:
|
||||||
@ -795,10 +792,7 @@ class Logics:
|
|||||||
num_satoshis = cls.payout_amount(order, user)[1]["invoice_amount"]
|
num_satoshis = cls.payout_amount(order, user)[1]["invoice_amount"]
|
||||||
if mining_fee_rate:
|
if mining_fee_rate:
|
||||||
# not a valid mining fee
|
# not a valid mining fee
|
||||||
min_mining_fee_rate = LNNode.estimate_fee(
|
min_mining_fee_rate = get_minning_fee("minimum", num_satoshis)
|
||||||
amount_sats=num_satoshis,
|
|
||||||
target_conf=config("MINIMUM_TARGET_CONF", cast=int, default=24),
|
|
||||||
)["mining_fee_rate"]
|
|
||||||
|
|
||||||
min_mining_fee_rate = max(2, min_mining_fee_rate)
|
min_mining_fee_rate = max(2, min_mining_fee_rate)
|
||||||
|
|
||||||
|
@ -11,7 +11,6 @@ from api.utils import get_session
|
|||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
|
||||||
help = "Polls telegram /getUpdates method"
|
help = "Polls telegram /getUpdates method"
|
||||||
rest = 3 # seconds between consecutive polls
|
rest = 3 # seconds between consecutive polls
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ from django.utils import timezone
|
|||||||
|
|
||||||
|
|
||||||
class Currency(models.Model):
|
class Currency(models.Model):
|
||||||
|
|
||||||
with open("frontend/static/assets/currencies.json") as f:
|
with open("frontend/static/assets/currencies.json") as f:
|
||||||
currency_dict = json.load(f)
|
currency_dict = json.load(f)
|
||||||
currency_choices = [(int(val), label) for val, label in list(currency_dict.items())]
|
currency_choices = [(int(val), label) for val, label in list(currency_dict.items())]
|
||||||
|
10
api/tasks.py
10
api/tasks.py
@ -98,15 +98,9 @@ def send_devfund_donation(order_id, proceeds, reason):
|
|||||||
|
|
||||||
from api.lightning.node import LNNode
|
from api.lightning.node import LNNode
|
||||||
from api.models import LNPayment, Order
|
from api.models import LNPayment, Order
|
||||||
|
from api.utils import get_devfund_pubkey
|
||||||
|
|
||||||
if config("NETWORK", cast=str) == "testnet":
|
target_pubkey = get_devfund_pubkey(config("NETWORK", cast=str))
|
||||||
target_pubkey = (
|
|
||||||
"03ecb271b3e2e36f2b91c92c65bab665e5165f8cdfdada1b5f46cfdd3248c87fd6"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
target_pubkey = (
|
|
||||||
"02187352cc4b1856b9604e0a79e1bc9b301be7e0c14acbbb8c29f7051d507127d7"
|
|
||||||
)
|
|
||||||
|
|
||||||
order = Order.objects.get(id=order_id)
|
order = Order.objects.get(id=order_id)
|
||||||
coordinator_alias = config("COORDINATOR_ALIAS", cast=str, default="NoAlias")
|
coordinator_alias = config("COORDINATOR_ALIAS", cast=str, default="NoAlias")
|
||||||
|
90
api/utils.py
90
api/utils.py
@ -70,6 +70,96 @@ def validate_onchain_address(address):
|
|||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
|
|
||||||
|
mining_fee = {}
|
||||||
|
|
||||||
|
|
||||||
|
@ring.dict(mining_fee, expire=60) # keeps in cache for 60 seconds
|
||||||
|
def get_minning_fee(priority: str, preliminary_amount: int) -> int:
|
||||||
|
"""
|
||||||
|
priority: (str) 'suggested' | 'minimum'
|
||||||
|
Fetches suggested and minimum mining fee rates from mempool.space
|
||||||
|
uses LND/CLN fee estimator as fallback.
|
||||||
|
|
||||||
|
mempool.space response object:
|
||||||
|
{
|
||||||
|
fastestFee: 1,
|
||||||
|
halfHourFee: 1,
|
||||||
|
hourFee: 1,
|
||||||
|
economyFee: 1,
|
||||||
|
minimumFee: 1
|
||||||
|
}
|
||||||
|
Where 'suggested' is 'fastestFee' and 'minimum' is 'economyFee'
|
||||||
|
"""
|
||||||
|
|
||||||
|
from api.lightning.node import LNNode
|
||||||
|
|
||||||
|
session = get_session()
|
||||||
|
mempool_url = (
|
||||||
|
"http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion"
|
||||||
|
if USE_TOR
|
||||||
|
else "https://mempool.space"
|
||||||
|
)
|
||||||
|
api_path = "/api/v1/fees/recommended"
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = session.get(mempool_url + api_path)
|
||||||
|
response.raise_for_status() # Raises stored HTTPError, if one occurred
|
||||||
|
data = response.json()
|
||||||
|
if priority == "suggested":
|
||||||
|
value = data.get("fastestFee")
|
||||||
|
elif priority == "minimum":
|
||||||
|
value = data.get("economyFee")
|
||||||
|
else:
|
||||||
|
raise Exception(
|
||||||
|
"an error occurred",
|
||||||
|
"unexpected value for mining fee priority",
|
||||||
|
priority,
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
# Fetch mining fee from LND/CLN instance
|
||||||
|
if priority == "suggested":
|
||||||
|
target_conf = config("SUGGESTED_TARGET_CONF", cast=int, default=2)
|
||||||
|
if priority == "minimum":
|
||||||
|
target_conf = config("MINIMUM_TARGET_CONF", cast=int, default=24)
|
||||||
|
|
||||||
|
value = LNNode.estimate_fee(
|
||||||
|
amount_sats=preliminary_amount,
|
||||||
|
target_conf=target_conf,
|
||||||
|
)["mining_fee_rate"]
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
devfund_pubkey = {}
|
||||||
|
|
||||||
|
|
||||||
|
@ring.dict(devfund_pubkey, expire=3600) # keeps in cache for 3600 seconds
|
||||||
|
def get_devfund_pubkey(network: str) -> str:
|
||||||
|
"""
|
||||||
|
network: (str) "mainnet" | "testnet";
|
||||||
|
Fetches devfund pubkey from `main` branch in the repository
|
||||||
|
fallback to hardcoded pubkey
|
||||||
|
"""
|
||||||
|
|
||||||
|
session = get_session()
|
||||||
|
url = "https://raw.githubusercontent.com/RoboSats/robosats/main/devfund_pubey.json"
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = session.get(url)
|
||||||
|
response.raise_for_status() # Raises stored HTTPError, if one occurred
|
||||||
|
value = response.json().get(network)
|
||||||
|
if len(value) != 66:
|
||||||
|
raise Exception()
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
with open("devfund_pubkey.json", "r") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
value = data.get(network)
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
market_cache = {}
|
market_cache = {}
|
||||||
|
|
||||||
|
|
||||||
|
@ -913,7 +913,7 @@ class LimitView(ListAPIView):
|
|||||||
def get(self, request):
|
def get(self, request):
|
||||||
# Trade limits as BTC
|
# Trade limits as BTC
|
||||||
min_trade = config("MIN_ORDER_SIZE", cast=int, default=20_000) / 100_000_000
|
min_trade = config("MIN_ORDER_SIZE", cast=int, default=20_000) / 100_000_000
|
||||||
max_trade = config("MAX_ORDER_SIZE", cast=int, default=5_000_000) / 100_000_000
|
max_trade = config("MAX_ORDER_SIZE", cast=int, default=500_000) / 100_000_000
|
||||||
|
|
||||||
payload = {}
|
payload = {}
|
||||||
queryset = Currency.objects.all().order_by("currency")
|
queryset = Currency.objects.all().order_by("currency")
|
||||||
|
@ -8,7 +8,6 @@ from control.models import AccountingDay, BalanceLog
|
|||||||
|
|
||||||
@admin.register(AccountingDay)
|
@admin.register(AccountingDay)
|
||||||
class AccountingDayAdmin(ImportExportModelAdmin):
|
class AccountingDayAdmin(ImportExportModelAdmin):
|
||||||
|
|
||||||
list_display = (
|
list_display = (
|
||||||
"day",
|
"day",
|
||||||
"contracted",
|
"contracted",
|
||||||
@ -34,7 +33,6 @@ class AccountingDayAdmin(ImportExportModelAdmin):
|
|||||||
|
|
||||||
@admin.register(BalanceLog)
|
@admin.register(BalanceLog)
|
||||||
class BalanceLogAdmin(ImportExportModelAdmin):
|
class BalanceLogAdmin(ImportExportModelAdmin):
|
||||||
|
|
||||||
list_display = (
|
list_display = (
|
||||||
"time",
|
"time",
|
||||||
"total",
|
"total",
|
||||||
|
4
devfund_pubkey.json
Normal file
4
devfund_pubkey.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"mainnet": "02187352cc4b1856b9604e0a79e1bc9b301be7e0c14acbbb8c29f7051d507127d7",
|
||||||
|
"testnet": "03ecb271b3e2e36f2b91c92c65bab665e5165f8cdfdada1b5f46cfdd3248c87fd6"
|
||||||
|
}
|
@ -1,5 +1,9 @@
|
|||||||
[tool.isort]
|
[tool.ruff]
|
||||||
profile = "black"
|
# Exclude a variety of commonly ignored directories.
|
||||||
|
exclude = [
|
||||||
|
"*migrations/*",
|
||||||
|
"api/nick_generator/nick_generator.py",
|
||||||
|
]
|
||||||
|
|
||||||
[tool.coverage.run]
|
[tool.coverage.run]
|
||||||
omit = [
|
omit = [
|
||||||
|
Reference in New Issue
Block a user