mirror of
https://github.com/aljazceru/plugins.git
synced 2025-12-24 00:24:19 +01:00
currencyrate: new plugin to do currency conversions.
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
committed by
Christian Decker
parent
bfb1cf5d27
commit
db5ca7f64a
7
currencyrate/Makefile
Normal file
7
currencyrate/Makefile
Normal file
@@ -0,0 +1,7 @@
|
||||
#! /usr/bin/make
|
||||
|
||||
check:
|
||||
@# E501 line too long (N > 79 characters)
|
||||
@# E731 do not assign a lambda expression, use a def
|
||||
@# W503: line break before binary operator
|
||||
@flake8 --ignore=E501,E731,W503 *.py
|
||||
41
currencyrate/README.md
Normal file
41
currencyrate/README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Currencyrate plugin
|
||||
|
||||
This plugin provides Bitcoin currency conversion functions using various
|
||||
different backends and taking the median. It caches results for an hour.
|
||||
|
||||
## Installation
|
||||
|
||||
For general plugin installation instructions see the repos main
|
||||
[README.md](https://github.com/lightningd/plugins/blob/master/README.md#Installation)
|
||||
|
||||
## Options:
|
||||
|
||||
* --add-source: Add a source, of form NAME,URL,MEMBERS where URL and MEMBERS
|
||||
can have `{currency}` and `{currency_lc}` to substitute for upper-case and
|
||||
lower-case currency names. MEMBERS is how to deconstruct the result, for
|
||||
example if the result is `{"USD": {"last_trade": 12456.79}}` then MEMBERS
|
||||
would be "USD,last_trade".
|
||||
* --disable-source: Disable the source with this name.
|
||||
|
||||
## Commands
|
||||
|
||||
`currencyrate` returns the number of msats per unit from every backend, eg:
|
||||
|
||||
```
|
||||
$ lightning-cli currencyrate USD
|
||||
{
|
||||
"localbitcoins": "5347227msat",
|
||||
"bitstamp": "5577515msat",
|
||||
"coingecko": "5579273msat",
|
||||
}
|
||||
```
|
||||
|
||||
`currencyconvert` converts the given amount and currency into msats, using the
|
||||
median from the above results. eg:
|
||||
|
||||
```
|
||||
$ lightning-cli currencyconvert 100 USD
|
||||
{
|
||||
"msat": "515941800msat"
|
||||
}
|
||||
```
|
||||
119
currencyrate/currencyrate.py
Executable file
119
currencyrate/currencyrate.py
Executable file
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
from pyln.client import Plugin
|
||||
from collections import namedtuple
|
||||
from pyln.client import Millisatoshi
|
||||
from cachetools import cached, TTLCache
|
||||
import requests
|
||||
import statistics
|
||||
import time
|
||||
|
||||
plugin = Plugin()
|
||||
|
||||
Source = namedtuple('Source', ['name', 'urlformat', 'replymembers'])
|
||||
|
||||
sources = [
|
||||
# e.g. {"GBP": {"volume_btc": "24.36647424", "rates": {"last": "13667.63"}, "avg_1h": "13786.86", "avg_6h": "13723.65", "avg_12h": "13680.23", "avg_24h": "13739.56"}, "USD": {"volume_btc": "27.97517017", "rates": {"last": "18204.21"}, "avg_1h": "19349.46", "avg_6h": "18621.72", "avg_12h": "18642.28", "avg_24h": "18698.94"}
|
||||
# ...
|
||||
# "GTQ": {"volume_btc": "0.03756101", "rates": {"last": "148505.46"}, "avg_1h": "148505.46", "avg_6h": "162463.69", "avg_12h": "162003.10", "avg_24h": "162003.10"}, "DKK": {"volume_btc": "0.00339923", "rates": {"last": "139737.53"}, "avg_12h": "139737.53", "avg_24h": "139737.53"}, "HTG": {"volume_btc": "0.00024758", "rates": {"last": "2019549.24"}, "avg_6h": "2019549.24", "avg_12h": "2019549.24", "avg_24h": "2019549.24"}, "NAD": {"volume_btc": "0.00722222", "rates": {"last": "360000.11"}, "avg_12h": "360000.11", "avg_24h": "360000.11"}}
|
||||
Source('localbitcoins',
|
||||
'https://localbitcoins.com/bitcoinaverage/ticker-all-currencies/',
|
||||
['{currency}', "avg_6h"]),
|
||||
# e.g. {"high": "18502.56", "last": "17970.41", "timestamp": "1607650787", "bid": "17961.87", "vwap": "18223.42", "volume": "7055.63066541", "low": "17815.92", "ask": "17970.41", "open": "18250.30"}
|
||||
Source('bitstamp',
|
||||
'https://www.bitstamp.net/api/v2/ticker/btc{currency_lc}/',
|
||||
['last']),
|
||||
# e.g. {"bitcoin":{"usd":17885.84}}
|
||||
Source('coingecko',
|
||||
'https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies={currency_lc}',
|
||||
['bitcoin', '{currency_lc}']),
|
||||
]
|
||||
|
||||
|
||||
def get_currencyrate(plugin, currency, req_template, response_members):
|
||||
# NOTE: Bitstamp has a DNS/Proxy issues that can return 404
|
||||
# Workaround: retry up to 5 times with a delay
|
||||
currency_lc = currency.lower()
|
||||
url = req_template.format(currency_lc=currency_lc, currency=currency)
|
||||
for _ in range(5):
|
||||
r = requests.get(url, proxies=plugin.proxies)
|
||||
if r.status_code != 200:
|
||||
time.sleep(1)
|
||||
continue
|
||||
break
|
||||
|
||||
if r.status_code != 200:
|
||||
plugin.log(level='info', message='{}: bad response {}'.format(url, r.status_code))
|
||||
return None
|
||||
|
||||
json = r.json()
|
||||
for m in response_members:
|
||||
expanded = m.format(currency_lc=currency_lc, currency=currency)
|
||||
if expanded not in json:
|
||||
plugin.log(level='debug', message='{}: {} not in {}'.format(url, expanded, json))
|
||||
return None
|
||||
json = json[expanded]
|
||||
|
||||
try:
|
||||
return Millisatoshi(int(10**11 / float(json)))
|
||||
except Exception:
|
||||
plugin.log(level='info', message='{}: could not convert {} to msat'.format(url, json))
|
||||
return None
|
||||
|
||||
|
||||
def set_proxies(plugin):
|
||||
config = plugin.rpc.listconfigs()
|
||||
if 'always-use-proxy' in config and config['always-use-proxy']:
|
||||
paddr = config['proxy']
|
||||
# Default port in 9050
|
||||
if ':' not in paddr:
|
||||
paddr += ':9050'
|
||||
plugin.proxies = {'https': 'socks5h://' + paddr,
|
||||
'http': 'socks5h://' + paddr}
|
||||
else:
|
||||
plugin.proxies = None
|
||||
|
||||
|
||||
# Don't grab these more than once per hour.
|
||||
@cached(cache=TTLCache(maxsize=1024, ttl=3600))
|
||||
def get_rates(plugin, currency):
|
||||
rates = {}
|
||||
for s in sources:
|
||||
r = get_currencyrate(plugin, currency, s.urlformat, s.replymembers)
|
||||
if r is not None:
|
||||
rates[s.name] = r
|
||||
|
||||
return rates
|
||||
|
||||
|
||||
@plugin.method("currencyrate")
|
||||
def currencyrate(plugin, currency):
|
||||
"""Gets currency from given APIs."""
|
||||
|
||||
return get_rates(plugin, currency.upper())
|
||||
|
||||
|
||||
@plugin.method("currencyconvert")
|
||||
def currencyconvert(plugin, amount, currency):
|
||||
"""Converts currency using given APIs."""
|
||||
val = statistics.median([m.millisatoshis for m in get_rates(plugin, currency.upper()).values()]) * float(amount)
|
||||
return {"msat": Millisatoshi(round(val))}
|
||||
|
||||
|
||||
@plugin.init()
|
||||
def init(options, configuration, plugin):
|
||||
set_proxies(plugin)
|
||||
|
||||
if options['add-source'] != '':
|
||||
parts = options['add-source'].split(',')
|
||||
sources.append(Source(parts[0], parts[1], parts[2:]))
|
||||
|
||||
if options['disable-source'] != '':
|
||||
for s in sources[:]:
|
||||
if s.name == options['disable-source']:
|
||||
sources.remove(s)
|
||||
|
||||
|
||||
# As a bad example: binance,https://api.binance.com/api/v3/ticker/price?symbol=BTC{currency}T,price
|
||||
plugin.add_option(name='add-source', default='', description='Add source name,urlformat,resultmembers...')
|
||||
plugin.add_option(name='disable-source', default='', description='Disable source by name')
|
||||
plugin.run()
|
||||
5
currencyrate/requirements.txt
Normal file
5
currencyrate/requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
pyln-client>=0.7.3
|
||||
requests>=2.10.0
|
||||
requests[socks]>=2.10.0
|
||||
packaging>=14.1
|
||||
cachetools
|
||||
Reference in New Issue
Block a user