initial upload
This commit is contained in:
commit
9f67d3a6ee
201
cfg/config.go
Normal file
201
cfg/config.go
Normal file
@ -0,0 +1,201 @@
|
||||
package cfg
|
||||
|
||||
import (
|
||||
"dero-swap/coin"
|
||||
"dero-swap/dero"
|
||||
"dero-swap/monero"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/ybbus/jsonrpc/v3"
|
||||
)
|
||||
|
||||
// load config file
|
||||
func LoadConfig() {
|
||||
|
||||
fd, err := os.ReadFile("config.json")
|
||||
if err != nil {
|
||||
log.Printf("Error loading config file: %v\n", err)
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal(fd, &Settings)
|
||||
if err != nil {
|
||||
log.Printf("Error parsing config file: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
dero.Dero_Daemon = jsonrpc.NewClient("http://" + Settings.Dero_daemon + "/json_rpc")
|
||||
dero.Dero_Wallet = jsonrpc.NewClient("http://" + Settings.Dero_wallet + "/json_rpc")
|
||||
monero.Monero_Wallet = jsonrpc.NewClient("http://" + Settings.Monero_wallet + "/json_rpc")
|
||||
|
||||
coin.XTC_URL[coin.BTCDERO] = "http://" + Settings.BTC_daemon
|
||||
coin.XTC_URL[coin.LTCDERO] = "http://" + Settings.LTC_daemon
|
||||
coin.XTC_URL[coin.ARRRDERO] = "http://" + Settings.ARRR_daemon
|
||||
|
||||
// check if pair is "supported"
|
||||
for _, p := range Settings.Pairs {
|
||||
supported := false
|
||||
for i := range coin.Supported_pairs {
|
||||
if p == coin.Supported_pairs[i] {
|
||||
supported = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if supported {
|
||||
coin.Pairs[p] = true
|
||||
} else {
|
||||
log.Printf("%s is not a supported pair\n", p)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("Config successfully loaded\n")
|
||||
|
||||
LoadFees()
|
||||
}
|
||||
|
||||
// load fees file
|
||||
func LoadFees() {
|
||||
|
||||
fd, err := os.ReadFile("fees.json")
|
||||
if err != nil {
|
||||
log.Printf("Error loading fees file: %v\n", err)
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal(fd, &SwapFees)
|
||||
if err != nil {
|
||||
log.Printf("Error parsing fees file: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("%-14s: Buy: %.2f%% / Sell: %.2f%%\n", "Fees", SwapFees.Swap.Ask, SwapFees.Swap.Bid)
|
||||
}
|
||||
|
||||
// basic config check
|
||||
func CheckConfig() bool {
|
||||
|
||||
if Settings.Dero_daemon == "" || Settings.Dero_wallet == "" {
|
||||
log.Println("Dero Daemon or Dero Wallet is not set")
|
||||
return false
|
||||
}
|
||||
|
||||
for p := range coin.Pairs {
|
||||
switch p {
|
||||
case coin.BTCDERO, coin.DEROBTC:
|
||||
if Settings.BTC_daemon == "" || Settings.BTC_dir == "" {
|
||||
log.Printf("%s pair is set, but daemon or directory is not set\n", p)
|
||||
return false
|
||||
} else {
|
||||
coin.BTC_dir = Settings.BTC_dir
|
||||
}
|
||||
case coin.LTCDERO, coin.DEROLTC:
|
||||
if Settings.LTC_daemon == "" || Settings.LTC_dir == "" {
|
||||
log.Printf("%s pair is set, but daemon or directory is not set\n", p)
|
||||
return false
|
||||
} else {
|
||||
coin.LTC_dir = Settings.LTC_dir
|
||||
}
|
||||
case coin.ARRRDERO, coin.DEROARRR:
|
||||
if Settings.ARRR_daemon == "" || Settings.ARRR_dir == "" {
|
||||
log.Printf("%s pair is set, but daemon or directory is not set\n", p)
|
||||
return false
|
||||
} else {
|
||||
coin.ARRR_dir = Settings.ARRR_dir
|
||||
}
|
||||
case coin.XMRDERO, coin.DEROXMR:
|
||||
if Settings.Monero_wallet == "" {
|
||||
log.Printf("%s pair is set, but wallet is not set\n", p)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if dero.GetHeight() == 0 || dero.CheckBlockHeight() == 0 {
|
||||
log.Println("Dero daemon or wallet is not available")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// TODO: optimization needed
|
||||
func LoadWallets() {
|
||||
|
||||
for p := range coin.Pairs {
|
||||
switch p {
|
||||
case coin.ARRRDERO, coin.DEROARRR:
|
||||
addr := coin.ARRR_GetAddress()
|
||||
if !coin.XTCValidateAddress(p, addr) {
|
||||
log.Printf("Disable pair \"%s\": wallet not available or other error\n", p)
|
||||
delete(coin.Pairs, p)
|
||||
} else {
|
||||
if coin.ARRR_address == "" {
|
||||
coin.ARRR_address = addr
|
||||
coin.SimplePairs[p] = true
|
||||
log.Printf("%-14s: %s\n", "ARRR Wallet", addr)
|
||||
}
|
||||
}
|
||||
case coin.XMRDERO, coin.DEROXMR:
|
||||
addr := monero.GetAddress()
|
||||
if !monero.ValidateAddress(addr) {
|
||||
log.Printf("Disable pair \"%s\": wallet not available or other error\n", p)
|
||||
delete(coin.Pairs, p)
|
||||
} else {
|
||||
if coin.XMR_address == "" {
|
||||
coin.XMR_address = addr
|
||||
coin.SimplePairs[p] = true
|
||||
log.Printf("%-14s: %s\n", "XMR Wallet", addr)
|
||||
}
|
||||
}
|
||||
case coin.LTCDERO, coin.DEROLTC:
|
||||
ok, err := coin.XTCLoadWallet(p)
|
||||
if !ok && !strings.Contains(err, "is already loaded") {
|
||||
ok, err := coin.XTCNewWallet(p)
|
||||
if !ok {
|
||||
log.Printf("Disable pair \"%s\": %s\n", p, err)
|
||||
delete(coin.Pairs, p)
|
||||
} else {
|
||||
addr := coin.XTCGetAddress(p)
|
||||
if coin.LTC_address == "" {
|
||||
coin.LTC_address = addr
|
||||
coin.SimplePairs[p] = true
|
||||
log.Printf("%-14s: %s\n", "LTC Wallet", addr)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
addr := coin.XTCGetAddress(p)
|
||||
if coin.LTC_address == "" {
|
||||
coin.LTC_address = addr
|
||||
coin.SimplePairs[p] = true
|
||||
log.Printf("%-14s: %s\n", "LTC Wallet", addr)
|
||||
}
|
||||
}
|
||||
case coin.BTCDERO, coin.DEROBTC:
|
||||
ok, err := coin.XTCLoadWallet(p)
|
||||
if !ok && !strings.Contains(err, "is already loaded") {
|
||||
ok, err := coin.XTCNewWallet(p)
|
||||
if !ok {
|
||||
log.Printf("Disable pair \"%s\": %s\n", p, err)
|
||||
delete(coin.Pairs, p)
|
||||
} else {
|
||||
addr := coin.XTCGetAddress(p)
|
||||
if coin.BTC_address == "" {
|
||||
coin.BTC_address = addr
|
||||
coin.SimplePairs[p] = true
|
||||
log.Printf("%-14s: %s\n", "BTC Wallet", addr)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
addr := coin.XTCGetAddress(p)
|
||||
if coin.BTC_address == "" {
|
||||
coin.BTC_address = addr
|
||||
coin.SimplePairs[p] = true
|
||||
log.Printf("%-14s: %s\n", "BTC Wallet", addr)
|
||||
}
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
49
cfg/variables.go
Normal file
49
cfg/variables.go
Normal file
@ -0,0 +1,49 @@
|
||||
package cfg
|
||||
|
||||
type Config struct {
|
||||
ListenAddress string `json:"listen"`
|
||||
BTC_daemon string `json:"btc_daemon"`
|
||||
BTC_dir string `json:"btc_dir"`
|
||||
LTC_daemon string `json:"ltc_daemon"`
|
||||
LTC_dir string `json:"ltc_dir"`
|
||||
ARRR_daemon string `json:"arrr_daemon"`
|
||||
ARRR_dir string `json:"arrr_dir"`
|
||||
Dero_daemon string `json:"dero_daemon"`
|
||||
Dero_wallet string `json:"dero_wallet"`
|
||||
Monero_daemon string `json:"monero_daemon"`
|
||||
Monero_wallet string `json:"monero_wallet"`
|
||||
Pairs []string `json:"pairs"`
|
||||
//SwapFees float64 `json:"swap_fees"`
|
||||
}
|
||||
|
||||
type (
|
||||
Fees struct {
|
||||
Swap Swap_Fees `json:"swap"`
|
||||
Withdrawal Withdrawal_Fees `json:"withdrawal"`
|
||||
}
|
||||
/*
|
||||
Swap_Fees struct {
|
||||
DeroLTC float64 `json:"dero-ltc"`
|
||||
DeroBTC float64 `json:"dero-btc"`
|
||||
DeroARRR float64 `json:"dero-arrr"`
|
||||
DeroXMR float64 `json:"dero-xmr"`
|
||||
LTCDero float64 `json:"ltc-dero"`
|
||||
BTCDero float64 `json:"btc-dero"`
|
||||
ARRRDero float64 `json:"arrr-dero"`
|
||||
XMRDero float64 `json:"xmr-dero"`
|
||||
}
|
||||
*/
|
||||
Swap_Fees struct {
|
||||
Bid float64 `json:"bid"`
|
||||
Ask float64 `json:"ask"`
|
||||
}
|
||||
Withdrawal_Fees struct {
|
||||
DeroLTC float64 `json:"dero-ltc"`
|
||||
DeroBTC float64 `json:"dero-btc"`
|
||||
DeroARRR float64 `json:"dero-arrr"`
|
||||
DeroXMR float64 `json:"dero-xmr"`
|
||||
}
|
||||
)
|
||||
|
||||
var Settings Config
|
||||
var SwapFees = Fees{Withdrawal: Withdrawal_Fees{DeroLTC: 0.0002, DeroBTC: 0.00004, DeroARRR: 0.001, DeroXMR: 0.00006}}
|
545
coin/btc.go
Normal file
545
coin/btc.go
Normal file
@ -0,0 +1,545 @@
|
||||
package coin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func XTCGetCookie(pair string) bool {
|
||||
|
||||
var data []byte
|
||||
var err error
|
||||
|
||||
switch pair {
|
||||
case BTCDERO, DEROBTC:
|
||||
if data, err = os.ReadFile(BTC_dir + "/.cookie"); err != nil {
|
||||
log.Printf("Can't load BTC auth cookie: %v\n", err)
|
||||
return false
|
||||
}
|
||||
case LTCDERO, DEROLTC:
|
||||
if data, err = os.ReadFile(LTC_dir + "/.cookie"); err != nil {
|
||||
log.Printf("Can't load LTC auth cookie: %v\n", err)
|
||||
return false
|
||||
}
|
||||
case ARRRDERO, DEROARRR:
|
||||
if data, err = os.ReadFile(ARRR_dir + "/.cookie"); err != nil {
|
||||
log.Printf("Can't load ARRR auth cookie: %v\n", err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
XTC_auth = base64.StdEncoding.EncodeToString(data)
|
||||
data = nil
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func SetHeaders(request *http.Request, auth string) {
|
||||
|
||||
request.Header.Add("Content-Type", "text/plain")
|
||||
request.Header.Add("Authorization", fmt.Sprintf("Basic %s", auth))
|
||||
}
|
||||
|
||||
func XTCBuildRequest(pair string, method string, options []interface{}) (*http.Request, error) {
|
||||
|
||||
json_object := &RPC_Request{Jsonrpc: "1.0", Id: "swap", Method: method, Params: options}
|
||||
json_bytes, err := json.Marshal(&json_object)
|
||||
if err != nil {
|
||||
log.Printf("Can't marshal %s request: %v\n", method, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", XTC_GetURL(pair), bytes.NewBuffer(json_bytes))
|
||||
if err != nil {
|
||||
log.Printf("Can't create %s request: %v\n", method, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
XTCGetCookie(pair)
|
||||
SetHeaders(req, XTC_auth)
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func XTCNewWallet(pair string) (result bool, message string) {
|
||||
|
||||
switch pair {
|
||||
case DEROARRR, ARRRDERO, DEROXMR, XMRDERO:
|
||||
return false, ""
|
||||
}
|
||||
req, err := XTCBuildRequest(pair, "createwallet", []interface{}{"swap_wallet"})
|
||||
if err != nil {
|
||||
log.Printf("Can't build createwallet request: %v\n", err)
|
||||
return false, ""
|
||||
}
|
||||
|
||||
resp, err := XTC_Daemon.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("Can't send createwallet request: %v\n", err)
|
||||
return false, ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var json_resonse RPC_NewWallet_Response
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
err = json.Unmarshal(body, &json_resonse)
|
||||
if err != nil {
|
||||
log.Printf("Can't unmarshal createwallet response: %v\n", err)
|
||||
return false, ""
|
||||
}
|
||||
|
||||
if json_resonse.Error != (RPC_Error{}) {
|
||||
message = json_resonse.Error.Message
|
||||
} else {
|
||||
result = true
|
||||
}
|
||||
|
||||
return result, message
|
||||
}
|
||||
|
||||
func XTCLoadWallet(pair string) (result bool, message string) {
|
||||
|
||||
req, err := XTCBuildRequest(pair, "loadwallet", []interface{}{"swap_wallet"})
|
||||
if err != nil {
|
||||
log.Printf("Can't build loadwallet request: %v\n", err)
|
||||
return false, ""
|
||||
}
|
||||
|
||||
resp, err := XTC_Daemon.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("Can't send loadwallet request: %v\n", err)
|
||||
return false, ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var json_resonse RPC_NewWallet_Response
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
err = json.Unmarshal(body, &json_resonse)
|
||||
if err != nil {
|
||||
log.Printf("Can't unmarshal createwallet response: %v\n", err)
|
||||
return false, ""
|
||||
}
|
||||
|
||||
if json_resonse.Error != (RPC_Error{}) {
|
||||
message = json_resonse.Error.Message
|
||||
} else {
|
||||
result = true
|
||||
}
|
||||
|
||||
return result, message
|
||||
}
|
||||
|
||||
func XTCNewAddress(pair string) string {
|
||||
|
||||
req, err := XTCBuildRequest(pair, "getnewaddress", []interface{}{})
|
||||
if err != nil {
|
||||
log.Printf("Can't build getnewaddress request: %v\n", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
resp, err := XTC_Daemon.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("Can't send getnewaddress request: %v\n", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
var json_resonse RPC_NewAddress_Response
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
err = json.Unmarshal(body, &json_resonse)
|
||||
if err != nil {
|
||||
log.Printf("Can't unmarshal getnewaddress response: %v\n", err)
|
||||
return ""
|
||||
}
|
||||
log.Println("Successfully created new address")
|
||||
|
||||
return json_resonse.Result
|
||||
}
|
||||
|
||||
func XTCGetAddress(pair string) string {
|
||||
|
||||
switch pair {
|
||||
case DEROARRR, ARRRDERO, DEROXMR, XMRDERO:
|
||||
return ""
|
||||
}
|
||||
|
||||
_, _, address, _ := XTCListReceivedByAddress(pair, "", 0, 0, true)
|
||||
|
||||
return address
|
||||
}
|
||||
|
||||
func XTCCheckBlockHeight(pair string) uint64 {
|
||||
|
||||
req, err := XTCBuildRequest(pair, "getblockcount", []interface{}{})
|
||||
if err != nil {
|
||||
log.Printf("Can't build getblockcount request: %v\n", err)
|
||||
return 0
|
||||
}
|
||||
|
||||
resp, err := XTC_Daemon.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("Can't send getblockcount request: %v\n", err)
|
||||
return 0
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
var response RPC_GetBlockCount_Response
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
err = json.Unmarshal(body, &response)
|
||||
if err != nil {
|
||||
log.Printf("Can't unmarshal getblockcount response: %v\n", err)
|
||||
return 0
|
||||
} else {
|
||||
return response.Result
|
||||
}
|
||||
}
|
||||
|
||||
func XTCReceivedByAddress(pair string, wallet string) (float64, error) {
|
||||
|
||||
req, err := XTCBuildRequest(pair, "getreceivedbyaddress", []interface{}{wallet, 2})
|
||||
if err != nil {
|
||||
log.Printf("Can't build getreceivedbyaddress request: %v\n", err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
resp, err := XTC_Daemon.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("Can't send getreceivedbyaddress request: %v\n", err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
var response RPC_ReceivedByAddress_Response
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
err = json.Unmarshal(body, &response)
|
||||
if err != nil {
|
||||
log.Printf("Can't unmarshal getreceivedbyaddress response: %v\n", err)
|
||||
return 0, err
|
||||
} else {
|
||||
return response.Result, nil
|
||||
}
|
||||
}
|
||||
|
||||
func XTCListReceivedByAddress(pair string, wallet string, amount float64, height uint64, get_address bool) (bool, bool, string, error) {
|
||||
|
||||
var method string
|
||||
var options []interface{}
|
||||
|
||||
if !get_address {
|
||||
if pair == DEROARRR {
|
||||
method = "zs_listreceivedbyaddress"
|
||||
options = append(options, wallet, 1, 3, height)
|
||||
} else {
|
||||
method = "listreceivedbyaddress"
|
||||
options = append(options, 1, false, false, wallet)
|
||||
}
|
||||
} else {
|
||||
method = "listreceivedbyaddress"
|
||||
options = append(options, 0, true)
|
||||
}
|
||||
|
||||
req, err := XTCBuildRequest(pair, method, options)
|
||||
if err != nil {
|
||||
log.Printf("Can't build listreceivedbyaddress request: %v\n", err)
|
||||
return false, false, "", err
|
||||
}
|
||||
|
||||
resp, err := XTC_Daemon.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("Can't send listreceivedbyaddress request: %v\n", err)
|
||||
return false, false, "", err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if pair != DEROARRR {
|
||||
var response RPC_ListReceivedByAddress_Response
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
err = json.Unmarshal(body, &response)
|
||||
if err != nil {
|
||||
log.Printf("Can't unmarshal listreceivedbyaddress response: %v\n", err)
|
||||
return false, false, "", err
|
||||
}
|
||||
|
||||
if !get_address {
|
||||
if len(response.Result) > 0 {
|
||||
for _, tx := range response.Result[0].Txids {
|
||||
tx_data, err := XTCGetTransaction(pair, tx)
|
||||
if err != nil {
|
||||
log.Printf("Error checking TX: %v\n", err)
|
||||
continue
|
||||
}
|
||||
if tx_data.Result.Amount == amount && tx_data.Result.Blockheight >= height {
|
||||
if tx_data.Result.Confirmations > 1 {
|
||||
return true, true, "", nil
|
||||
} else {
|
||||
return false, true, "", nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if len(response.Result) > 0 {
|
||||
return false, false, response.Result[0].Address, nil
|
||||
} else {
|
||||
return false, false, "", nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var response RPC_ARRR_ListReceivedByAddress_Response
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
err = json.Unmarshal(body, &response)
|
||||
if err != nil {
|
||||
log.Printf("Can't unmarshal zs_listreceivedbyaddress response: %v\n", err)
|
||||
return false, false, "", err
|
||||
}
|
||||
|
||||
if len(response.Result) > 0 {
|
||||
for e := range response.Result {
|
||||
if response.Result[e].BlockHeight >= height {
|
||||
for _, tx := range response.Result[e].Received {
|
||||
if tx.Value == amount {
|
||||
if response.Result[e].Confirmations > 1 {
|
||||
return true, true, "", nil
|
||||
} else {
|
||||
return false, true, "", nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false, false, "", nil
|
||||
}
|
||||
|
||||
func XTCGetTransaction(pair string, txid string) (result RPC_GetTransaction_Response, err error) {
|
||||
|
||||
req, err := XTCBuildRequest(pair, "gettransaction", []interface{}{txid, false})
|
||||
if err != nil {
|
||||
log.Printf("Can't build gettransaction request: %v\n", err)
|
||||
return result, err
|
||||
}
|
||||
|
||||
resp, err := XTC_Daemon.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("Can't send gettransaction request: %v\n", err)
|
||||
return result, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
err = json.Unmarshal(body, &result)
|
||||
if err != nil {
|
||||
log.Printf("Can't unmarshal gettransaction response: %v\n", err)
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func XTCGetBalance(pair string) float64 {
|
||||
|
||||
var method string
|
||||
var options []interface{}
|
||||
if pair == DEROARRR || pair == ARRRDERO {
|
||||
method = "z_getbalance"
|
||||
options = append(options, ARRR_GetAddress())
|
||||
} else {
|
||||
method = "getbalance"
|
||||
}
|
||||
|
||||
req, err := XTCBuildRequest(pair, method, options)
|
||||
if err != nil {
|
||||
log.Printf("Can't build getbalance request: %v\n", err)
|
||||
return 0
|
||||
}
|
||||
|
||||
resp, err := XTC_Daemon.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("Can't send getbalance request: %v\n", err)
|
||||
return 0
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
var result RPC_GetBalance_Result
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
err = json.Unmarshal(body, &result)
|
||||
if err != nil {
|
||||
log.Printf("Can't unmarshal getbalance response: %v\n", err)
|
||||
return 0
|
||||
}
|
||||
|
||||
return RoundFloat(result.Result-Locked.GetLockedBalance(pair), 8)
|
||||
}
|
||||
|
||||
func XTCSend(pair string, wallet string, amount float64) (bool, string) {
|
||||
|
||||
var options []interface{}
|
||||
|
||||
switch pair {
|
||||
case DEROLTC:
|
||||
options = append(options, wallet, amount)
|
||||
case DEROBTC:
|
||||
options = append(options, wallet, amount, "", "", false, true, nil, "unset", nil, 14)
|
||||
}
|
||||
|
||||
req, err := XTCBuildRequest(pair, "sendtoaddress", options)
|
||||
if err != nil {
|
||||
log.Printf("Can't build sendtoaddress request: %v\n", err)
|
||||
return false, ""
|
||||
}
|
||||
|
||||
resp, err := XTC_Daemon.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("Can't send sendtoaddress request: %v\n", err)
|
||||
return false, ""
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
var result RPC_Send_Result
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
err = json.Unmarshal(body, &result)
|
||||
if err != nil {
|
||||
log.Printf("Can't unmarshal sendtoaddress response: %v\n", err)
|
||||
return false, ""
|
||||
}
|
||||
|
||||
return true, result.Result
|
||||
}
|
||||
|
||||
func XTCValidateAddress(pair string, address string) bool {
|
||||
|
||||
var method string
|
||||
if pair == DEROARRR || pair == ARRRDERO {
|
||||
method = "z_validateaddress"
|
||||
} else {
|
||||
method = "validateaddress"
|
||||
}
|
||||
|
||||
req, err := XTCBuildRequest(pair, method, []interface{}{address})
|
||||
if err != nil {
|
||||
log.Printf("Can't build validateaddress request: %v\n", err)
|
||||
return false
|
||||
}
|
||||
|
||||
resp, err := XTC_Daemon.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("Can't send validateaddress request: %v\n", err)
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var result RPC_Validate_Address_Result
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
err = json.Unmarshal(body, &result)
|
||||
if err != nil {
|
||||
log.Printf("Can't unmarshal validateaddress response: %v\n", err)
|
||||
return false
|
||||
}
|
||||
|
||||
return result.Result.IsValid
|
||||
}
|
||||
|
||||
func XTC_GetURL(pair string) string {
|
||||
|
||||
switch pair {
|
||||
case BTCDERO, DEROBTC:
|
||||
return XTC_URL[BTCDERO]
|
||||
case LTCDERO, DEROLTC:
|
||||
return XTC_URL[LTCDERO]
|
||||
case ARRRDERO, DEROARRR:
|
||||
return XTC_URL[ARRRDERO]
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func ARRR_Send(wallet string, amount float64) (bool, string) {
|
||||
|
||||
var params []RPC_ARRR_SendMany_Params
|
||||
|
||||
params = append(params, RPC_ARRR_SendMany_Params{Address: wallet, Amount: amount})
|
||||
options := []interface{}{ARRR_GetAddress(), params}
|
||||
|
||||
req, err := XTCBuildRequest(DEROARRR, "z_sendmany", options)
|
||||
if err != nil {
|
||||
log.Printf("Can't build z_sendmany request: %v\n", err)
|
||||
return false, ""
|
||||
}
|
||||
|
||||
resp, err := XTC_Daemon.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("Can't send z_sendmany request: %v\n", err)
|
||||
return false, ""
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
var result RPC_Send_Result
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
err = json.Unmarshal(body, &result)
|
||||
if err != nil {
|
||||
log.Printf("Can't unmarshal z_sendmany response: %v\n", err)
|
||||
return false, ""
|
||||
}
|
||||
|
||||
return true, result.Result
|
||||
}
|
||||
|
||||
func ARRR_GetAddress() string {
|
||||
|
||||
req, err := XTCBuildRequest(DEROARRR, "z_listaddresses", nil)
|
||||
if err != nil {
|
||||
log.Printf("Can't build z_listaddresses request: %v\n", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
resp, err := XTC_Daemon.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("Can't send z_listaddresses request: %v\n", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
var result RPC_ARRR_ListAddresses
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
err = json.Unmarshal(body, &result)
|
||||
if err != nil {
|
||||
log.Printf("Can't unmarshal z_listaddresses response: %v\n", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
if len(result.Result) == 0 {
|
||||
return ""
|
||||
} else {
|
||||
return result.Result[0]
|
||||
}
|
||||
}
|
12
coin/constants.go
Normal file
12
coin/constants.go
Normal file
@ -0,0 +1,12 @@
|
||||
package coin
|
||||
|
||||
const (
|
||||
ARRRDERO = "arrr-dero"
|
||||
BTCDERO = "btc-dero"
|
||||
LTCDERO = "ltc-dero"
|
||||
XMRDERO = "xmr-dero"
|
||||
DEROLTC = "dero-ltc"
|
||||
DEROBTC = "dero-btc"
|
||||
DEROARRR = "dero-arrr"
|
||||
DEROXMR = "dero-xmr"
|
||||
)
|
130
coin/functions.go
Normal file
130
coin/functions.go
Normal file
@ -0,0 +1,130 @@
|
||||
package coin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
)
|
||||
|
||||
func IsPairEnabled(coin string) bool {
|
||||
for i := range Pairs {
|
||||
if coin == i {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func IsAmountFree(coin string, amount float64) bool {
|
||||
|
||||
dir_entries, err := os.ReadDir("swaps/active")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var swap_e Swap_Entry
|
||||
|
||||
for _, e := range dir_entries {
|
||||
file_data, err := os.ReadFile("swaps/active/" + e.Name())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
err = json.Unmarshal(file_data, &swap_e)
|
||||
if err != nil || (swap_e.Price == amount && swap_e.Coin == coin) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *Swap) GetLockedBalance(coin string) float64 {
|
||||
r.RLock()
|
||||
defer r.RUnlock()
|
||||
|
||||
switch coin {
|
||||
case BTCDERO, LTCDERO, ARRRDERO:
|
||||
return r.Dero_balance
|
||||
case DEROLTC:
|
||||
return r.LTC_balance
|
||||
case DEROBTC:
|
||||
return r.BTC_balance
|
||||
case DEROARRR:
|
||||
return r.ARRR_balance
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Swap) AddLockedBalance(coin string, amount float64) {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
switch coin {
|
||||
case BTCDERO, LTCDERO, ARRRDERO:
|
||||
r.Dero_balance += amount
|
||||
case DEROLTC:
|
||||
r.LTC_balance += amount
|
||||
case DEROBTC:
|
||||
r.BTC_balance += amount
|
||||
case DEROARRR:
|
||||
r.ARRR_balance += amount
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Swap) RemoveLockedBalance(coin string, amount float64) {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
switch coin {
|
||||
case BTCDERO, LTCDERO, ARRRDERO:
|
||||
r.Dero_balance -= amount
|
||||
case DEROLTC:
|
||||
r.LTC_balance -= amount
|
||||
case DEROBTC:
|
||||
r.BTC_balance -= amount
|
||||
case DEROARRR:
|
||||
r.ARRR_balance -= amount
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Swap) LoadLockedBalance() {
|
||||
|
||||
dir_entries, err := os.ReadDir("swaps/active")
|
||||
if err != nil {
|
||||
ErrorCheckingOpenSwaps()
|
||||
}
|
||||
|
||||
var swap_e Swap_Entry
|
||||
var amount float64
|
||||
|
||||
for _, e := range dir_entries {
|
||||
file_data, err := os.ReadFile("swaps/active/" + e.Name())
|
||||
if err != nil {
|
||||
ErrorCheckingOpenSwaps()
|
||||
}
|
||||
err = json.Unmarshal(file_data, &swap_e)
|
||||
if err != nil {
|
||||
ErrorCheckingOpenSwaps()
|
||||
}
|
||||
switch swap_e.Coin {
|
||||
case LTCDERO, BTCDERO, ARRRDERO, XMRDERO:
|
||||
amount += swap_e.Amount
|
||||
r.AddLockedBalance(swap_e.Coin, swap_e.Amount)
|
||||
default:
|
||||
r.AddLockedBalance(swap_e.Coin, swap_e.Price)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ErrorCheckingOpenSwaps() {
|
||||
log.Println("Can't check reserved amounts")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// round value to X decimal places
|
||||
func RoundFloat(value float64, precision uint) float64 {
|
||||
ratio := math.Pow(10, float64(precision))
|
||||
return math.Round(value*ratio) / ratio
|
||||
}
|
146
coin/rpc.go
Normal file
146
coin/rpc.go
Normal file
@ -0,0 +1,146 @@
|
||||
package coin
|
||||
|
||||
// RPC request / response
|
||||
type (
|
||||
RPC_Request struct {
|
||||
Jsonrpc string `json:"jsonrpc"`
|
||||
Id string `json:"id"`
|
||||
Method string `json:"method"`
|
||||
Params []interface{} `json:"params"`
|
||||
}
|
||||
RPC_Response struct {
|
||||
Error RPC_Error `json:"error"`
|
||||
}
|
||||
RPC_Send_Result struct {
|
||||
Result string `json:"result"`
|
||||
}
|
||||
RPC_Error struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
)
|
||||
|
||||
// BTC/LTC related RPC structures
|
||||
type (
|
||||
RPC_NewWallet_Response struct {
|
||||
Result RPC_NewWallet `json:"result"`
|
||||
Error RPC_Error `json:"error"`
|
||||
}
|
||||
RPC_NewWallet struct {
|
||||
Name string `json:"name"`
|
||||
Warning string `json:"warning"`
|
||||
}
|
||||
RPC_NewAddress_Response struct {
|
||||
Result string `json:"result"`
|
||||
Error RPC_Error `json:"error"`
|
||||
}
|
||||
RPC_GetBlockCount_Response struct {
|
||||
Result uint64 `json:"result"`
|
||||
Error RPC_Error `json:"error"`
|
||||
}
|
||||
RPC_ReceivedByAddress_Response struct {
|
||||
Result float64 `json:"result"`
|
||||
Error RPC_Error `json:"error"`
|
||||
}
|
||||
RPC_ListReceivedByAddress_Response struct {
|
||||
Result []RPC_ListReceivedByAddress `json:"result"`
|
||||
}
|
||||
RPC_ListReceivedByAddress struct {
|
||||
InvolvesWatchonly bool `json:"involvesWatchonly"`
|
||||
Address string `json:"address"`
|
||||
Amount float64 `json:"amount"`
|
||||
Confirmations uint64 `json:"confirmations"`
|
||||
Label string `json:"label"`
|
||||
Txids []string `json:"txids"`
|
||||
}
|
||||
RPC_GetTransaction_Response struct {
|
||||
Result RPC_GetTransaction `json:"result"`
|
||||
}
|
||||
RPC_GetTransaction struct {
|
||||
Amount float64 `json:"amount"`
|
||||
Fee float64 `json:"fee"`
|
||||
Confirmations uint64 `json:"confirmations"`
|
||||
Generated bool `json:"generated"`
|
||||
Trusted bool `json:"trusted"`
|
||||
Blockhash string `json:"blockhash"`
|
||||
Blockheight uint64 `json:"blockheight"`
|
||||
Blockindex uint64 `json:"blockindex"`
|
||||
Blocktime uint64 `json:"blocktime"`
|
||||
Txid string `json:"txid"`
|
||||
Walletconflicts []string `json:"walletconflicts"`
|
||||
Time uint64 `json:"time"`
|
||||
TimeReceived uint64 `json:"timereceived"`
|
||||
Comment string `json:"comment"`
|
||||
Bip125Replaceable string `json:"bip125-replaceable"`
|
||||
Detail []RPC_Details `json:"details"`
|
||||
Hex string `json:"hex"`
|
||||
}
|
||||
RPC_GetBalance_Result struct {
|
||||
Result float64 `json:"result"`
|
||||
}
|
||||
RPC_Details struct {
|
||||
InvolvesWatchonly bool `json:"involvesWatchonly"`
|
||||
Address string `json:"address"`
|
||||
Category string `json:"category"`
|
||||
Amount float64 `json:"amount"`
|
||||
Label string `json:"label"`
|
||||
Vout float64 `json:"vout"`
|
||||
Fee float64 `json:"fee"`
|
||||
Abandoned bool `json:"abandoned"`
|
||||
}
|
||||
RPC_Validate_Address struct {
|
||||
IsValid bool `json:"isvalid"`
|
||||
Address string `json:"address"`
|
||||
ScriptPubKey string `json:"scriptPubKey"`
|
||||
Isscript bool `json:"isscript"`
|
||||
Iswitness bool `json:"iswitness"`
|
||||
Witness_version float64 `json:"witness_version,omitempty"`
|
||||
Witness_program string `json:"witness_program,omitempty"`
|
||||
}
|
||||
RPC_Validate_Address_Result struct {
|
||||
Result RPC_Validate_Address `json:"result"`
|
||||
}
|
||||
)
|
||||
|
||||
// ARRR related RPC structures
|
||||
type (
|
||||
RPC_ARRR_ListReceivedByAddress_Response struct {
|
||||
Result []RPC_ARRR_ListReceivedByAddress `json:"result"`
|
||||
}
|
||||
RPC_ARRR_ListReceivedByAddress_Details struct {
|
||||
Type string `json:"type"`
|
||||
Output uint64 `json:"output"`
|
||||
Outgoing bool `json:"outgoing"`
|
||||
Address string `json:"address"`
|
||||
Value float64 `json:"value"`
|
||||
ValueZat uint64 `json:"valueZat"`
|
||||
Change bool `json:"change"`
|
||||
Spendable bool `json:"spendable"`
|
||||
Memo string `json:"memo"`
|
||||
MemoStr string `json:"memoStr"`
|
||||
}
|
||||
RPC_ARRR_ListAddresses struct {
|
||||
Result []string `json:"result"`
|
||||
}
|
||||
RPC_ARRR_ListReceivedByAddress struct {
|
||||
Txid string `json:"txid"`
|
||||
Coinbase bool `json:"coinbase"`
|
||||
Category string `json:"category"`
|
||||
BlockHeight uint64 `json:"blockHeight"`
|
||||
Blockhash string `json:"blockhash"`
|
||||
Blockindex uint64 `json:"blockindex"`
|
||||
Blocktime uint64 `json:"blocktime"`
|
||||
RawConfirmations uint64 `json:"rawconfirmations"`
|
||||
Confirmations uint64 `json:"confirmations"`
|
||||
Time uint64 `json:"time"`
|
||||
ExpiryHeight uint64 `json:"expiryHeight"`
|
||||
Size uint64 `json:"size"`
|
||||
Fee uint64 `json:"fee"`
|
||||
Received []RPC_ARRR_ListReceivedByAddress_Details `json:"received"`
|
||||
}
|
||||
RPC_ARRR_SendMany_Params struct {
|
||||
Address string `json:"address"`
|
||||
Amount float64 `json:"amount"`
|
||||
Memo string `json:"memo,omitempty"`
|
||||
}
|
||||
)
|
52
coin/variables.go
Normal file
52
coin/variables.go
Normal file
@ -0,0 +1,52 @@
|
||||
package coin
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type (
|
||||
Swap_Request struct {
|
||||
Pair string `json:"pair"`
|
||||
Amount float64 `json:"amount"`
|
||||
DeroAddr string `json:"dero_address"`
|
||||
}
|
||||
Swap_Response struct {
|
||||
ID int64 `json:"id"`
|
||||
Wallet string `json:"wallet,omitempty"`
|
||||
Deposit float64 `json:"deposit,omitempty"`
|
||||
Swap float64 `json:"swap,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Request Swap_Request `json:"request"`
|
||||
}
|
||||
Swap_Entry struct {
|
||||
Coin string `json:"coin"`
|
||||
Wallet string `json:"wallet"`
|
||||
Destination string `json:"destination"`
|
||||
Amount float64 `json:"amount"`
|
||||
Price float64 `json:"price"`
|
||||
Created int64 `json:"created"`
|
||||
Block uint64 `json:"block"`
|
||||
Balance float64 `json:"balance"`
|
||||
Status uint64 `json:"status"`
|
||||
}
|
||||
Swap struct {
|
||||
Dero_balance float64
|
||||
LTC_balance float64
|
||||
BTC_balance float64
|
||||
ARRR_balance float64
|
||||
XMR_balance float64
|
||||
sync.RWMutex
|
||||
}
|
||||
)
|
||||
|
||||
var Locked Swap
|
||||
var Supported_pairs = []string{BTCDERO, LTCDERO, ARRRDERO, XMRDERO, DEROBTC, DEROLTC, DEROARRR, DEROXMR}
|
||||
var Pairs = make(map[string]bool)
|
||||
var SimplePairs = make(map[string]bool)
|
||||
var XTC_URL = make(map[string]string)
|
||||
var XTC_Daemon = &http.Client{}
|
||||
var XTC_auth string
|
||||
|
||||
var BTC_address, LTC_address, ARRR_address, XMR_address string
|
||||
var BTC_dir, LTC_dir, ARRR_dir string
|
129
dero/daemon.go
Normal file
129
dero/daemon.go
Normal file
@ -0,0 +1,129 @@
|
||||
package dero
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/deroproject/derohe/cryptography/crypto"
|
||||
"github.com/deroproject/derohe/rpc"
|
||||
"github.com/ybbus/jsonrpc/v3"
|
||||
)
|
||||
|
||||
var Dero_Daemon jsonrpc.RPCClient
|
||||
|
||||
func IsDeroAddressRegistered(address string) bool {
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
if result, err := Dero_Daemon.Call(ctx, "DERO.IsRegistered", &rpc.GetEncryptedBalance_Params{Address: address, SCID: crypto.ZEROHASH}); err != nil {
|
||||
cancel()
|
||||
log.Printf("Error checking registration status: %v\n", err)
|
||||
return false
|
||||
} else {
|
||||
cancel()
|
||||
|
||||
var response rpc.GetEncryptedBalance_Result
|
||||
|
||||
err = result.GetObject(&response)
|
||||
if err != nil {
|
||||
log.Printf("Error checking registration status: %v\n", err)
|
||||
return false
|
||||
}
|
||||
|
||||
return response.Registration > 0
|
||||
}
|
||||
}
|
||||
|
||||
func CheckWalletBalance() (amount float64, err error) {
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
if result, err := Dero_Wallet.Call(ctx, "GetBalance"); err != nil {
|
||||
cancel()
|
||||
log.Printf("Error checking Dero wallet balance: %v\n", err)
|
||||
return 0, err
|
||||
} else {
|
||||
cancel()
|
||||
|
||||
var response rpc.GetBalance_Result
|
||||
|
||||
err = result.GetObject(&response)
|
||||
if err != nil {
|
||||
log.Printf("Error checking Dero wallet balance: %v\n", err)
|
||||
return 0, err
|
||||
}
|
||||
return float64(response.Balance) / 100000, nil
|
||||
}
|
||||
}
|
||||
|
||||
func CheckBlockHeight() uint64 {
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
if result, err := Dero_Daemon.Call(ctx, "DERO.GetInfo"); err != nil {
|
||||
cancel()
|
||||
log.Printf("Error checking block height: %v\n", err)
|
||||
return 0
|
||||
} else {
|
||||
cancel()
|
||||
|
||||
var response rpc.GetInfo_Result
|
||||
|
||||
err = result.GetObject(&response)
|
||||
if err != nil {
|
||||
log.Printf("Error checking block height: %v\n", err)
|
||||
return 0
|
||||
}
|
||||
return uint64(response.Height)
|
||||
}
|
||||
}
|
||||
|
||||
func DEROCheckTX(txid string) (valid bool, err error) {
|
||||
|
||||
log.Println("Checking DERO transaction")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
if result, err := Dero_Daemon.Call(ctx, "DERO.GetTransaction", &rpc.GetTransaction_Params{Tx_Hashes: []string{txid}}); err != nil {
|
||||
cancel()
|
||||
log.Printf("Error getting transaction: %v\n", err)
|
||||
return false, err
|
||||
} else {
|
||||
cancel()
|
||||
|
||||
var response rpc.GetTransaction_Result
|
||||
|
||||
err = result.GetObject(&response)
|
||||
if err != nil {
|
||||
log.Printf("Error getting transaction: %v\n", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
if response.Txs[0].ValidBlock == "" || response.Txs == nil {
|
||||
return false, nil
|
||||
} else {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func CheckAddress(name string) string {
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
if result, err := Dero_Daemon.Call(ctx, "DERO.NameToAddress", &rpc.NameToAddress_Params{Name: name, TopoHeight: -1}); err != nil {
|
||||
cancel()
|
||||
log.Printf("Error checking name: %v\n", err)
|
||||
return ""
|
||||
} else {
|
||||
cancel()
|
||||
|
||||
var response rpc.NameToAddress_Result
|
||||
|
||||
err = result.GetObject(&response)
|
||||
if err != nil {
|
||||
log.Printf("Error checking name: %v\n", err)
|
||||
return ""
|
||||
}
|
||||
if response.Status == "OK" {
|
||||
return response.Address
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
192
dero/wallet.go
Normal file
192
dero/wallet.go
Normal file
@ -0,0 +1,192 @@
|
||||
package dero
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"dero-swap/coin"
|
||||
|
||||
"github.com/deroproject/derohe/cryptography/crypto"
|
||||
"github.com/deroproject/derohe/rpc"
|
||||
"github.com/ybbus/jsonrpc/v3"
|
||||
)
|
||||
|
||||
const atomicUnits float64 = 100000
|
||||
const TxFee float64 = 0.0004
|
||||
|
||||
var Dero_Wallet jsonrpc.RPCClient
|
||||
|
||||
func AddTX(wallet string, amount float64) rpc.Transfer {
|
||||
return rpc.Transfer{SCID: crypto.ZEROHASH, Destination: wallet, Amount: uint64(amount * atomicUnits)}
|
||||
}
|
||||
|
||||
func GetHeight() uint64 {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
result, err := Dero_Wallet.Call(ctx, "getheight")
|
||||
cancel()
|
||||
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
var response rpc.GetHeight_Result
|
||||
|
||||
err = result.GetObject(&response)
|
||||
if err != nil {
|
||||
log.Printf("Error getting DERO wallet height: %v\n", err)
|
||||
return 0
|
||||
}
|
||||
|
||||
return response.Height
|
||||
}
|
||||
|
||||
func GetBalance() float64 {
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
result, err := Dero_Wallet.Call(ctx, "getbalance")
|
||||
cancel()
|
||||
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
var response rpc.GetBalance_Result
|
||||
|
||||
err = result.GetObject(&response)
|
||||
if err != nil {
|
||||
log.Printf("Error getting DERO wallet balance: %v\n", err)
|
||||
return 0
|
||||
}
|
||||
|
||||
balance_fl := float64(response.Balance) / atomicUnits
|
||||
balance_fl -= coin.Locked.GetLockedBalance(coin.BTCDERO)
|
||||
|
||||
return coin.RoundFloat(balance_fl, 5)
|
||||
}
|
||||
|
||||
func Payout(tx []rpc.Transfer) {
|
||||
|
||||
var tries uint
|
||||
var reserve float64
|
||||
|
||||
for _, e := range tx {
|
||||
reserve += float64(e.Amount) / atomicUnits
|
||||
}
|
||||
|
||||
for {
|
||||
time.Sleep(time.Second * 18)
|
||||
|
||||
tries++
|
||||
if tries > 3 {
|
||||
log.Println("Too many failures, manual check necessary!")
|
||||
break
|
||||
}
|
||||
|
||||
log.Printf("Sending DERO transaction try #%d\n", tries)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
if result, err := Dero_Wallet.Call(ctx, "Transfer", &rpc.Transfer_Params{Ringsize: 2, Fees: 40, Transfers: tx}); err != nil {
|
||||
cancel()
|
||||
log.Printf("Error sending DERO transaction: %v\n", err)
|
||||
continue
|
||||
} else {
|
||||
cancel()
|
||||
|
||||
var response rpc.Transfer_Result
|
||||
|
||||
err = result.GetObject(&response)
|
||||
if err != nil {
|
||||
log.Printf("Error sending DERO transaction: %v\n", err)
|
||||
continue
|
||||
}
|
||||
if response.TXID == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var init_block, current_block uint64
|
||||
var retry bool
|
||||
|
||||
for init_block == 0 {
|
||||
init_block = CheckBlockHeight()
|
||||
time.Sleep(time.Second * 3)
|
||||
}
|
||||
current_block = init_block
|
||||
|
||||
retry = true
|
||||
for current_block-init_block <= 12 {
|
||||
ok, err := DEROCheckTX(response.TXID)
|
||||
if err != nil {
|
||||
time.Sleep(time.Second * 3)
|
||||
continue
|
||||
}
|
||||
if ok {
|
||||
log.Printf("DERO transaction (TXID %s) successfully sent\n", response.TXID)
|
||||
coin.Locked.RemoveLockedBalance(coin.BTCDERO, reserve)
|
||||
retry = false
|
||||
break
|
||||
}
|
||||
current_block = 0
|
||||
for current_block == 0 {
|
||||
current_block = CheckBlockHeight()
|
||||
time.Sleep(time.Second * 3)
|
||||
}
|
||||
time.Sleep(time.Second * 15)
|
||||
}
|
||||
if retry {
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func CheckIncomingTransfers(dstPort uint64, block uint64) bool {
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
result, err := Dero_Wallet.Call(ctx, "GetTransfers", &rpc.Get_Transfers_Params{In: true, Min_Height: block})
|
||||
cancel()
|
||||
if err != nil {
|
||||
log.Printf("Error sending DERO request: %v\n", err)
|
||||
return false
|
||||
}
|
||||
|
||||
var response rpc.Get_Transfers_Result
|
||||
|
||||
err = result.GetObject(&response)
|
||||
if err != nil {
|
||||
log.Printf("Error retrieving DERO response: %v\n", err)
|
||||
return false
|
||||
}
|
||||
|
||||
for _, e := range response.Entries {
|
||||
if e.DestinationPort == dstPort {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func MakeIntegratedAddress(sessionID int64) string {
|
||||
|
||||
var payload = rpc.Arguments{rpc.Argument{Name: "D", DataType: "U", Value: uint64(sessionID)}}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
result, err := Dero_Wallet.Call(ctx, "MakeIntegratedAddress", &rpc.Make_Integrated_Address_Params{Payload_RPC: payload})
|
||||
cancel()
|
||||
if err != nil {
|
||||
log.Printf("Error sending DERO request: %v\n", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
var response rpc.Make_Integrated_Address_Result
|
||||
|
||||
err = result.GetObject(&response)
|
||||
if err != nil {
|
||||
log.Printf("Error retrieving DERO response: %v\n", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
return response.Integrated_Address
|
||||
}
|
50
extras.go
Normal file
50
extras.go
Normal file
@ -0,0 +1,50 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type backoff struct {
|
||||
userlist map[string]int64
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
var Delay = backoff{userlist: make(map[string]int64)}
|
||||
|
||||
func (u *backoff) AddUser(address string) {
|
||||
|
||||
u.Lock()
|
||||
defer u.Unlock()
|
||||
|
||||
u.userlist[address] = time.Now().UnixMilli()
|
||||
}
|
||||
|
||||
func (u *backoff) DelUser(address string) {
|
||||
delete(u.userlist, address)
|
||||
}
|
||||
|
||||
func (u *backoff) CheckUser(address string) bool {
|
||||
|
||||
u.Lock()
|
||||
defer u.Unlock()
|
||||
|
||||
if _, ok := u.userlist[address]; ok {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (u *backoff) CheckBackoff() {
|
||||
|
||||
u.Lock()
|
||||
defer u.Unlock()
|
||||
|
||||
for i, e := range u.userlist {
|
||||
added := time.UnixMilli(e)
|
||||
if time.Since(added) >= time.Minute*2 {
|
||||
Delay.DelUser(i)
|
||||
}
|
||||
}
|
||||
}
|
35
go.mod
Normal file
35
go.mod
Normal file
@ -0,0 +1,35 @@
|
||||
module dero-swap
|
||||
|
||||
go 1.21.5
|
||||
|
||||
require (
|
||||
github.com/deroproject/derohe v0.0.0-20240215152352-a5a0e6a68ada
|
||||
github.com/lesismal/llib v1.1.12
|
||||
github.com/lesismal/nbio v1.5.3
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/ybbus/jsonrpc/v3 v3.1.5
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/caarlos0/env/v6 v6.10.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/deroproject/graviton v0.0.0-20220130070622-2c248a53b2e1 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.6.0 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/zapr v1.3.0 // indirect
|
||||
github.com/kr/pretty v0.3.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.8.0 // indirect
|
||||
github.com/satori/go.uuid v1.2.0 // indirect
|
||||
github.com/stretchr/testify v1.8.4 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/crypto v0.19.0 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
42
main.go
Normal file
42
main.go
Normal file
@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"dero-swap/cfg"
|
||||
"dero-swap/coin"
|
||||
|
||||
"github.com/robfig/cron/v3"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
|
||||
// create all swap directories
|
||||
os.MkdirAll("swaps/active", 0755)
|
||||
os.MkdirAll("swaps/expired", 0755)
|
||||
os.MkdirAll("swaps/done", 0755)
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
Init()
|
||||
cfg.LoadConfig()
|
||||
if !cfg.CheckConfig() {
|
||||
log.Println("Configuration error. Please check config file!")
|
||||
os.Exit(1)
|
||||
}
|
||||
cfg.LoadWallets()
|
||||
|
||||
coin.Locked.LoadLockedBalance()
|
||||
|
||||
UpdateMarkets()
|
||||
|
||||
c := cron.New()
|
||||
c.AddFunc("@every 1m", UpdateMarkets)
|
||||
c.AddFunc("@every 2m", Delay.CheckBackoff)
|
||||
c.Start()
|
||||
|
||||
go Swap_Controller()
|
||||
StartServer()
|
||||
}
|
113
monero/rpc.go
Normal file
113
monero/rpc.go
Normal file
@ -0,0 +1,113 @@
|
||||
package monero
|
||||
|
||||
// XMR related RPC structures
|
||||
type (
|
||||
RPC_XMR_Height struct {
|
||||
Height uint64 `json:"height"`
|
||||
}
|
||||
RPC_XMR_GetBalance_Params struct {
|
||||
AccountIndex uint64 `json:"account_index"`
|
||||
AddressIndices []uint64 `json:"address_indices,omitempty"`
|
||||
AllAccounts bool `json:"all_accounts"`
|
||||
Strict bool `json:"strict"`
|
||||
}
|
||||
RPC_XMR_GetBalance_Result struct {
|
||||
Balance uint64 `json:"balance"`
|
||||
UnlockedBalance uint64 `json:"unlocked_balance"`
|
||||
TimeToUnlock uint64 `json:"time_to_unlock"`
|
||||
BlocksToUnlock uint64 `json:"blocks_to_unlock"`
|
||||
PerSubaddress []RPC_XMR_Subaddress_Info `json:"per_subaddress"`
|
||||
}
|
||||
RPC_XMR_GetAddress_Params struct {
|
||||
AccountIndex uint64 `json:"account_index"`
|
||||
AddressIndex []uint64 `json:"address_index,omitempty"`
|
||||
}
|
||||
RPC_XMR_GetAddress_Result struct {
|
||||
Address string `json:"address"`
|
||||
Addresses []RPC_XMR_Addresses `json:"addresses"`
|
||||
}
|
||||
RPC_XMR_Addresses struct {
|
||||
Address string `json:"address"`
|
||||
Label string `json:"label"`
|
||||
AddressIndex uint64 `json:"address_index"`
|
||||
Used bool `json:"used"`
|
||||
}
|
||||
RPC_XMR_Subaddress_Info struct {
|
||||
AccountIndex uint64 `json:"account_index"`
|
||||
AddressIndices []uint64 `json:"address_indices,omitempty"`
|
||||
Address string `json:"address"`
|
||||
Balance uint64 `json:"balance"`
|
||||
UnlockedBalance uint64 `json:"unlocked_balance"`
|
||||
Label string `json:"label"`
|
||||
NumUnspentOutputs uint64 `json:"num_unspent_outputs"`
|
||||
TimeToUnlock uint64 `json:"time_to_unlock"`
|
||||
BlocksToUnlock uint64 `json:"blocks_to_unlock"`
|
||||
}
|
||||
RPC_XMR_IntegratedAddress_Result struct {
|
||||
IntegratedAddress string `json:"integrated_address"`
|
||||
PaymentID string `json:"payment_id"`
|
||||
}
|
||||
RPC_XMR_SplitIntegratedAddress_Params struct {
|
||||
IntegratedAddress string `json:"integrated_address"`
|
||||
}
|
||||
RPC_XMR_SplitIntegratedAddress_Result struct {
|
||||
StandardAddress string `json:"standard_address"`
|
||||
PaymentID string `json:"payment_id"`
|
||||
IsSubaddress bool `json:"is_subaddress"`
|
||||
}
|
||||
RPC_XMR_Validate_Address_Params struct {
|
||||
Address string `json:"address"`
|
||||
AnyNetType bool `json:"any_net_type"`
|
||||
Allow_Openalias bool `json:"allow_openalias"`
|
||||
}
|
||||
RPC_XMR_Validate_Address_Result struct {
|
||||
Valid bool `json:"valid"`
|
||||
Integrated bool `json:"integrated"`
|
||||
Subaddress bool `json:"subaddress"`
|
||||
Nettype string `json:"nettype"`
|
||||
OpenaliasAddress string `json:"openalias_address"`
|
||||
}
|
||||
RPC_XMR_Transfer_Params struct {
|
||||
Address string `json:"address"`
|
||||
Amount uint64 `json:"amount"`
|
||||
}
|
||||
RPC_XMR_Transfer struct {
|
||||
Destinations []RPC_XMR_Transfer_Params `json:"destinations"`
|
||||
AccountIndex uint64 `json:"account_index"`
|
||||
SubaddrIndices []uint64 `json:"subaddr_indices"`
|
||||
Ringsize uint64 `json:"ring_size"`
|
||||
UnlockTime uint64 `json:"unlock_time"`
|
||||
PaymentID string `json:"payment_id"`
|
||||
GetTXKey bool `json:"get_tx_keys"`
|
||||
Priority uint64 `json:"priority"`
|
||||
DoNotRelay bool `json:"do_not_relay"`
|
||||
GetTXHash bool `json:"get_tx_hash"`
|
||||
GetTxMetadata bool `json:"get_tx_metadata"`
|
||||
}
|
||||
RPC_XMR_Transfer_Result struct {
|
||||
Amount uint64 `json:"amount"`
|
||||
Fee uint64 `json:"fee"`
|
||||
MultiSig_TxSet string `json:"multisig_txset"`
|
||||
TxBlob string `json:"tx_blob"`
|
||||
TxHash string `json:"tx_hash"`
|
||||
TxKey string `json:"tx_key"`
|
||||
TxMetadata string `json:"tx_metadata"`
|
||||
Unsigned_TxSet string `json:"unsigned_txset"`
|
||||
}
|
||||
RPC_XMR_Payments struct {
|
||||
Address string `json:"address"`
|
||||
Amount uint64 `json:"amount"`
|
||||
BlockHeight uint64 `json:"block_height"`
|
||||
PaymentID string `json:"payment_id"`
|
||||
TxHash string `json:"tx_hash"`
|
||||
UnlockTime uint64 `json:"unlock_time"`
|
||||
Locked bool `json:"locked"`
|
||||
}
|
||||
RPC_XMR_GetPayments_Result struct {
|
||||
Payments []RPC_XMR_Payments `json:"payments"`
|
||||
}
|
||||
RPC_XMR_BulkTX_Params struct {
|
||||
Payment_IDs []string `json:"payment_ids"`
|
||||
MinBlockHeight uint64 `json:"min_block_height"`
|
||||
}
|
||||
)
|
193
monero/wallet.go
Normal file
193
monero/wallet.go
Normal file
@ -0,0 +1,193 @@
|
||||
package monero
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/ybbus/jsonrpc/v3"
|
||||
)
|
||||
|
||||
const atomicUnits float64 = 1000000000000
|
||||
|
||||
var Monero_Wallet jsonrpc.RPCClient
|
||||
|
||||
func GetHeight() uint64 {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
result, err := Monero_Wallet.Call(ctx, "get_height")
|
||||
cancel()
|
||||
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
var response RPC_XMR_Height
|
||||
|
||||
err = result.GetObject(&response)
|
||||
if err != nil {
|
||||
log.Printf("Error checking XMR wallet height: %v\n", err)
|
||||
return 0
|
||||
}
|
||||
|
||||
return response.Height
|
||||
}
|
||||
|
||||
func XMRGetTX(payment string, block uint64) bool {
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
result, err := Monero_Wallet.Call(ctx, "get_bulk_payments", RPC_XMR_BulkTX_Params{MinBlockHeight: block, Payment_IDs: []string{payment}})
|
||||
cancel()
|
||||
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var response RPC_XMR_GetPayments_Result
|
||||
|
||||
err = result.GetObject(&response)
|
||||
if err != nil {
|
||||
log.Printf("Error getting XMR incoming payments: %v\n", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// placeholder
|
||||
for _, p := range response.Payments {
|
||||
if p.UnlockTime > 0 {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func XMRSend(transfers []RPC_XMR_Transfer_Params) (ok bool, txid string) {
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
result, err := Monero_Wallet.Call(ctx, "transfer", RPC_XMR_Transfer{Destinations: transfers, Priority: 0, Ringsize: 16})
|
||||
cancel()
|
||||
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
var response RPC_XMR_Transfer_Result
|
||||
|
||||
err = result.GetObject(&response)
|
||||
if err != nil {
|
||||
log.Printf("Error sending XMR transaction: %v\n", err)
|
||||
return false, ""
|
||||
}
|
||||
|
||||
if response.TxHash != "" {
|
||||
return true, response.TxHash
|
||||
}
|
||||
return false, ""
|
||||
}
|
||||
|
||||
func GetBalance() float64 {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
result, err := Monero_Wallet.Call(ctx, "get_balance", RPC_XMR_GetBalance_Params{AccountIndex: 0})
|
||||
cancel()
|
||||
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
var response RPC_XMR_GetBalance_Result
|
||||
|
||||
err = result.GetObject(&response)
|
||||
if err != nil {
|
||||
log.Printf("Error getting XMR wallet balance: %v\n", err)
|
||||
return 0
|
||||
}
|
||||
|
||||
return float64(response.UnlockedBalance) / atomicUnits
|
||||
}
|
||||
|
||||
func GetAddress() string {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
result, err := Monero_Wallet.Call(ctx, "get_address", RPC_XMR_GetAddress_Params{AccountIndex: 0})
|
||||
cancel()
|
||||
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
var response RPC_XMR_GetAddress_Result
|
||||
|
||||
err = result.GetObject(&response)
|
||||
if err != nil {
|
||||
log.Printf("Error getting XMR wallet address: %v\n", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
return response.Address
|
||||
}
|
||||
|
||||
func MakeIntegratedAddress() string {
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
result, err := Monero_Wallet.Call(ctx, "make_integrated_address")
|
||||
cancel()
|
||||
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
var response RPC_XMR_IntegratedAddress_Result
|
||||
|
||||
err = result.GetObject(&response)
|
||||
if err != nil {
|
||||
log.Printf("Error generating XMR integrated address: %v\n", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
return response.IntegratedAddress
|
||||
}
|
||||
|
||||
func SplitIntegratedAddress(address string) string {
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
result, err := Monero_Wallet.Call(ctx, "split_integrated_address", RPC_XMR_SplitIntegratedAddress_Params{IntegratedAddress: address})
|
||||
cancel()
|
||||
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
var response RPC_XMR_SplitIntegratedAddress_Result
|
||||
|
||||
err = result.GetObject(&response)
|
||||
if err != nil {
|
||||
log.Printf("Error splitting XMR integrated address: %v\n", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
return response.PaymentID
|
||||
}
|
||||
|
||||
func ValidateAddress(address string) bool {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
result, err := Monero_Wallet.Call(ctx, "validate_address", RPC_XMR_Validate_Address_Params{Address: address})
|
||||
cancel()
|
||||
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var response RPC_XMR_Validate_Address_Result
|
||||
|
||||
err = result.GetObject(&response)
|
||||
if err != nil {
|
||||
log.Printf("Error validating XMR address: %v\n", err)
|
||||
return false
|
||||
}
|
||||
|
||||
return response.Valid
|
||||
}
|
||||
|
||||
func AddTX(wallet string, amount float64) RPC_XMR_Transfer_Params {
|
||||
return RPC_XMR_Transfer_Params{Address: wallet, Amount: uint64(amount * atomicUnits)}
|
||||
}
|
303
price.go
Normal file
303
price.go
Normal file
@ -0,0 +1,303 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"dero-swap/cfg"
|
||||
"dero-swap/coin"
|
||||
"dero-swap/dero"
|
||||
"dero-swap/monero"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetMarket(pair string, provider int) (prices Swap_Price) {
|
||||
|
||||
resp, err := http.Get(pair)
|
||||
if err != nil {
|
||||
log.Printf("Market: HTTP Get: %v\n", err)
|
||||
return Swap_Price{}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Printf("Market: Read Body: %v\n", err)
|
||||
return Swap_Price{}
|
||||
}
|
||||
|
||||
var market Price_Provider
|
||||
|
||||
if err := json.Unmarshal(body, &market); err != nil {
|
||||
log.Printf("Market: Cannot unmarshal data: %v\n", err)
|
||||
return Swap_Price{}
|
||||
}
|
||||
|
||||
switch provider {
|
||||
case TO:
|
||||
prices.Ask, err = strconv.ParseFloat(market.Ask, 64)
|
||||
prices.Bid, err = strconv.ParseFloat(market.Bid, 64)
|
||||
prices.Median, err = strconv.ParseFloat(market.Price, 64)
|
||||
case XEGGEX:
|
||||
prices.Ask, err = strconv.ParseFloat(market.BestAsk, 64)
|
||||
prices.Bid, err = strconv.ParseFloat(market.BestBid, 64)
|
||||
prices.Median, err = strconv.ParseFloat(market.LastPrice, 64)
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("Market: Cannot convert string: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Get Pair values
|
||||
// TODO: configurable fees; clean-up
|
||||
func GetPrice(pair string) (bid float64, ask float64) {
|
||||
|
||||
var base, base_usd Swap_Price
|
||||
var atomicUnits uint = 8
|
||||
var simple bool
|
||||
|
||||
switch pair {
|
||||
case coin.BTCDERO, coin.DEROBTC:
|
||||
simple = true
|
||||
for i := range DERO_BTC {
|
||||
if base = GetMarket(DERO_BTC[i], i); base.Ask > 0 && base.Bid > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case coin.LTCDERO, coin.DEROLTC:
|
||||
for i := range LTC_USDT {
|
||||
if base = GetMarket(LTC_USDT[i], i); base.Ask > 0 && base.Bid > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case coin.XMRDERO, coin.DEROXMR:
|
||||
atomicUnits = 12
|
||||
for i := range XMR_USDT {
|
||||
if base = GetMarket(XMR_USDT[i], i); base.Ask > 0 && base.Bid > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case coin.ARRRDERO, coin.DEROARRR:
|
||||
base = GetMarket(ARRR_USDT, TO)
|
||||
}
|
||||
|
||||
if base.Ask == 0 || base.Bid == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
if simple {
|
||||
bid = base.Bid - (base.Bid * cfg.SwapFees.Swap.Bid / 100)
|
||||
bid = coin.RoundFloat(bid, atomicUnits)
|
||||
ask = base.Ask + (base.Ask * cfg.SwapFees.Swap.Ask / 100)
|
||||
ask = coin.RoundFloat(ask, atomicUnits)
|
||||
return
|
||||
}
|
||||
|
||||
for i := range DERO_USDT {
|
||||
if base_usd = GetMarket(DERO_USDT[i], i); base_usd.Ask > 0 && base_usd.Bid > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if base_usd.Ask == 0 || base_usd.Bid == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
bid = base_usd.Bid - (base_usd.Bid * cfg.SwapFees.Swap.Bid / 100)
|
||||
ask = base_usd.Ask + (base_usd.Ask * cfg.SwapFees.Swap.Ask / 100)
|
||||
|
||||
bid = coin.RoundFloat(bid/base.Bid, atomicUnits)
|
||||
ask = coin.RoundFloat(ask/base.Ask, atomicUnits)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: simplify
|
||||
func UpdateMarkets() {
|
||||
|
||||
var btc, ltc, xmr, arrr float64
|
||||
var derobtc, deroltc, deroxmr, deroarrr float64
|
||||
|
||||
for p := range coin.SimplePairs {
|
||||
switch p {
|
||||
case coin.XMRDERO, coin.DEROXMR:
|
||||
deroxmr, xmr = GetPrice(p)
|
||||
case coin.ARRRDERO, coin.DEROARRR:
|
||||
deroarrr, arrr = GetPrice(p)
|
||||
case coin.LTCDERO, coin.DEROLTC:
|
||||
deroltc, ltc = GetPrice(p)
|
||||
case coin.BTCDERO, coin.DEROBTC:
|
||||
derobtc, btc = GetPrice(p)
|
||||
}
|
||||
}
|
||||
|
||||
// sometimes TradeOgre's BID/ASK values are swapped
|
||||
if derobtc > 0 && btc > 0 && derobtc > btc {
|
||||
swap := btc
|
||||
btc = derobtc
|
||||
derobtc = swap
|
||||
}
|
||||
if deroltc > 0 && ltc > 0 && deroltc > ltc {
|
||||
swap := ltc
|
||||
ltc = deroltc
|
||||
deroltc = swap
|
||||
}
|
||||
if deroarrr > 0 && arrr > 0 && deroarrr > arrr {
|
||||
swap := arrr
|
||||
arrr = deroarrr
|
||||
deroarrr = swap
|
||||
}
|
||||
if deroxmr > 0 && xmr > 0 && deroxmr > xmr {
|
||||
swap := xmr
|
||||
xmr = deroxmr
|
||||
deroxmr = swap
|
||||
}
|
||||
|
||||
mk.Lock()
|
||||
defer mk.Unlock()
|
||||
|
||||
// TODO: simplify
|
||||
if btc > 0 {
|
||||
mk.Pairs.BTC = btc
|
||||
mk.Update[coin.BTCDERO] = time.Now().UnixMilli()
|
||||
IsPairAvailable[coin.BTCDERO] = true
|
||||
} else {
|
||||
t := time.UnixMilli(mk.Update[coin.BTCDERO])
|
||||
if time.Since(t) > time.Minute*2 {
|
||||
IsPairAvailable[coin.BTCDERO] = false
|
||||
log.Println("BTC->DERO disabled")
|
||||
}
|
||||
}
|
||||
if ltc > 0 {
|
||||
mk.Pairs.LTC = ltc
|
||||
mk.Update[coin.LTCDERO] = time.Now().UnixMilli()
|
||||
IsPairAvailable[coin.LTCDERO] = true
|
||||
} else {
|
||||
t := time.UnixMilli(mk.Update[coin.LTCDERO])
|
||||
if time.Since(t) > time.Minute*2 {
|
||||
IsPairAvailable[coin.LTCDERO] = false
|
||||
log.Println("LTC->DERO disabled")
|
||||
}
|
||||
}
|
||||
if arrr > 0 {
|
||||
mk.Pairs.ARRR = arrr
|
||||
mk.Update[coin.ARRRDERO] = time.Now().UnixMilli()
|
||||
IsPairAvailable[coin.ARRRDERO] = true
|
||||
} else {
|
||||
t := time.UnixMilli(mk.Update[coin.ARRRDERO])
|
||||
if time.Since(t) > time.Minute*2 {
|
||||
IsPairAvailable[coin.ARRRDERO] = false
|
||||
log.Println("ARRR->DERO disabled")
|
||||
}
|
||||
}
|
||||
if xmr > 0 {
|
||||
mk.Pairs.XMR = xmr
|
||||
mk.Update[coin.XMRDERO] = time.Now().UnixMilli()
|
||||
IsPairAvailable[coin.XMRDERO] = true
|
||||
} else {
|
||||
t := time.UnixMilli(mk.Update[coin.XMRDERO])
|
||||
if time.Since(t) > time.Minute*2 {
|
||||
IsPairAvailable[coin.XMRDERO] = false
|
||||
log.Println("XMR->DERO disabled")
|
||||
}
|
||||
}
|
||||
if deroltc > 0 {
|
||||
mk.Pairs.DEROLTC = deroltc
|
||||
mk.Update[coin.DEROLTC] = time.Now().UnixMilli()
|
||||
IsPairAvailable[coin.DEROLTC] = true
|
||||
} else {
|
||||
t := time.UnixMilli(mk.Update[coin.DEROLTC])
|
||||
if time.Since(t) > time.Minute*2 {
|
||||
IsPairAvailable[coin.DEROLTC] = false
|
||||
log.Println("DERO->LTC disabled")
|
||||
}
|
||||
}
|
||||
if derobtc > 0 {
|
||||
mk.Pairs.DEROBTC = derobtc
|
||||
mk.Update[coin.DEROBTC] = time.Now().UnixMilli()
|
||||
IsPairAvailable[coin.DEROBTC] = true
|
||||
} else {
|
||||
t := time.UnixMilli(mk.Update[coin.DEROBTC])
|
||||
if time.Since(t) > time.Minute*2 {
|
||||
IsPairAvailable[coin.DEROBTC] = false
|
||||
log.Println("DERO->BTC disabled")
|
||||
}
|
||||
}
|
||||
if deroarrr > 0 {
|
||||
mk.Pairs.DEROARRR = deroarrr
|
||||
mk.Update[coin.DEROARRR] = time.Now().UnixMilli()
|
||||
IsPairAvailable[coin.DEROARRR] = true
|
||||
} else {
|
||||
t := time.UnixMilli(mk.Update[coin.DEROARRR])
|
||||
if time.Since(t) > time.Minute*2 {
|
||||
IsPairAvailable[coin.DEROARRR] = false
|
||||
log.Println("DERO->ARRR disabled")
|
||||
}
|
||||
}
|
||||
if deroxmr > 0 {
|
||||
mk.Pairs.DEROXMR = deroxmr
|
||||
mk.Update[coin.DEROXMR] = time.Now().UnixMilli()
|
||||
IsPairAvailable[coin.DEROXMR] = true
|
||||
} else {
|
||||
t := time.UnixMilli(mk.Update[coin.DEROXMR])
|
||||
if time.Since(t) > time.Minute*2 {
|
||||
IsPairAvailable[coin.DEROXMR] = false
|
||||
log.Println("DERO->XMR disabled")
|
||||
}
|
||||
}
|
||||
|
||||
var data bytes.Buffer
|
||||
var out WS_Message
|
||||
|
||||
out.Method = "market"
|
||||
out.Result = mk.Pairs
|
||||
encoder := json.NewEncoder(&data)
|
||||
|
||||
err := encoder.Encode(out)
|
||||
if err != nil {
|
||||
log.Printf("Market: %v\n", err)
|
||||
return
|
||||
}
|
||||
SendWSData(nil, data.Bytes())
|
||||
|
||||
data.Reset()
|
||||
out.Method = "balance"
|
||||
out.Result = UpdatePool()
|
||||
err = encoder.Encode(out)
|
||||
if err != nil {
|
||||
log.Printf("Balance: %v\n", err)
|
||||
return
|
||||
}
|
||||
SendWSData(nil, data.Bytes())
|
||||
}
|
||||
|
||||
// TODO: reduce function calls
|
||||
func UpdatePool() Swap_Balance {
|
||||
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
var balance Swap_Balance
|
||||
|
||||
balance.Dero = dero.GetBalance()
|
||||
|
||||
for p := range coin.SimplePairs {
|
||||
switch p {
|
||||
case coin.XMRDERO, coin.DEROXMR:
|
||||
balance.XMR = monero.GetBalance()
|
||||
case coin.ARRRDERO, coin.DEROARRR:
|
||||
balance.ARRR = coin.XTCGetBalance(p)
|
||||
case coin.LTCDERO, coin.DEROLTC:
|
||||
balance.LTC = coin.XTCGetBalance(p)
|
||||
case coin.BTCDERO, coin.DEROBTC:
|
||||
balance.BTC = coin.XTCGetBalance(p)
|
||||
}
|
||||
}
|
||||
|
||||
return balance
|
||||
}
|
73
price_variables.go
Normal file
73
price_variables.go
Normal file
@ -0,0 +1,73 @@
|
||||
package main
|
||||
|
||||
import "sync"
|
||||
|
||||
type (
|
||||
Price_Provider struct {
|
||||
// TradeOgre fields below
|
||||
Success bool `json:"success"`
|
||||
Init string `json:"initialprice"`
|
||||
Price string `json:"price"`
|
||||
High string `json:"high"`
|
||||
Low string `json:"low"`
|
||||
Volume string `json:"volume"`
|
||||
Bid string `json:"bid"`
|
||||
Ask string `json:"ask"`
|
||||
// Xeggex fields below
|
||||
BestAsk string `json:"bestAsk"`
|
||||
BestBid string `json:"bestBid"`
|
||||
LastPrice string `json:"lastPrice"`
|
||||
}
|
||||
Swap_Price struct {
|
||||
Ask float64
|
||||
Bid float64
|
||||
Median float64
|
||||
}
|
||||
Swap_Markets struct {
|
||||
BTC float64 `json:"btcdero,omitempty"`
|
||||
LTC float64 `json:"ltcdero,omitempty"`
|
||||
ARRR float64 `json:"arrrdero,omitempty"`
|
||||
XMR float64 `json:"xmrdero,omitempty"`
|
||||
DEROLTC float64 `json:"deroltc,omitempty"`
|
||||
DEROBTC float64 `json:"derobtc,omitempty"`
|
||||
DEROARRR float64 `json:"deroarrr,omitempty"`
|
||||
DEROXMR float64 `json:"deroxmr,omitempty"`
|
||||
}
|
||||
Swap_Balance struct {
|
||||
Dero float64 `json:"dero"`
|
||||
LTC float64 `json:"ltc,omitempty"`
|
||||
BTC float64 `json:"btc,omitempty"`
|
||||
ARRR float64 `json:"arrr,omitempty"`
|
||||
XMR float64 `json:"xmr,omitempty"`
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
ASK = iota
|
||||
BID = iota
|
||||
MEDIAN = iota
|
||||
)
|
||||
const (
|
||||
TO = iota
|
||||
XEGGEX = iota
|
||||
)
|
||||
const SATOSHI float64 = 1e-08
|
||||
|
||||
type MarketData struct {
|
||||
Pairs Swap_Markets
|
||||
Update map[string]int64
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
var mk = &MarketData{Update: make(map[string]int64)}
|
||||
var IsPairAvailable = make(map[string]bool)
|
||||
var lock sync.Mutex
|
||||
|
||||
// Price URLs
|
||||
var DERO_USDT = []string{"https://tradeogre.com/api/v1/ticker/DERO-USDT", "https://api.xeggex.com/api/v2/market/getbysymbol/DERO/USDT"}
|
||||
var DERO_BTC = []string{"https://tradeogre.com/api/v1/ticker/DERO-BTC", "https://api.xeggex.com/api/v2/market/getbysymbol/DERO/BTC"}
|
||||
var LTC_USDT = []string{"https://tradeogre.com/api/v1/ticker/LTC-USDT", "https://api.xeggex.com/api/v2/market/getbysymbol/LTC/USDT"}
|
||||
var XMR_USDT = []string{"https://tradeogre.com/api/v1/ticker/XMR-USDT", "https://api.xeggex.com/api/v2/market/getbysymbol/XMR/USDT"}
|
||||
var ARRR_USDT = "https://tradeogre.com/api/v1/ticker/ARRR-USDT"
|
||||
|
||||
type ORDER int
|
275
server.go
Normal file
275
server.go
Normal file
@ -0,0 +1,275 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/lesismal/llib/std/crypto/tls"
|
||||
|
||||
"github.com/deroproject/derohe/globals"
|
||||
"github.com/lesismal/nbio/nbhttp"
|
||||
"github.com/lesismal/nbio/nbhttp/websocket"
|
||||
|
||||
"dero-swap/cfg"
|
||||
"dero-swap/coin"
|
||||
"dero-swap/dero"
|
||||
)
|
||||
|
||||
type WS_Message struct {
|
||||
ID uint64 `json:"id"`
|
||||
Method string `json:"method"`
|
||||
Params any `json:"params,omitempty"`
|
||||
Result any `json:"result"`
|
||||
}
|
||||
|
||||
var WSConnections sync.Map
|
||||
|
||||
// swap other coins to Dero
|
||||
func Dero_Swap(request coin.Swap_Request) (response coin.Swap_Response) {
|
||||
|
||||
var err error
|
||||
|
||||
// check if destination wallet is valid. Registered usernames can also be used.
|
||||
if strings.HasPrefix(request.DeroAddr, "dero1") || strings.HasPrefix(request.DeroAddr, "deroi") {
|
||||
_, err = globals.ParseValidateAddress(request.DeroAddr)
|
||||
} else {
|
||||
if addr := dero.CheckAddress(request.DeroAddr); addr != "" {
|
||||
request.DeroAddr = addr
|
||||
} else {
|
||||
err = fmt.Errorf("invalid address")
|
||||
}
|
||||
}
|
||||
|
||||
// basic checks
|
||||
if request.Amount == 0 || err != nil {
|
||||
response.Error = "invalid request"
|
||||
return
|
||||
}
|
||||
|
||||
// prevent users from creating too many swap requests
|
||||
if Delay.CheckUser(request.DeroAddr) {
|
||||
response.Error = "2 minutes wait time triggered"
|
||||
return
|
||||
}
|
||||
|
||||
// check if pair is enabled and available
|
||||
pair := request.Pair
|
||||
if !coin.IsPairEnabled(pair) || !IsPairAvailable[pair] {
|
||||
response.Error = fmt.Sprintf("%s swap currently not possible", pair)
|
||||
return
|
||||
}
|
||||
|
||||
// create swap
|
||||
err = XTCSwap(pair, request.DeroAddr, coin.RoundFloat(request.Amount, 5), &response)
|
||||
|
||||
if err != nil {
|
||||
response.Error = err.Error()
|
||||
log.Println(err)
|
||||
} else {
|
||||
Delay.AddUser(request.DeroAddr)
|
||||
}
|
||||
response.Request = request
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
// swap Dero to other coins
|
||||
func Reverse_Swap(request coin.Swap_Request) (response coin.Swap_Response) {
|
||||
|
||||
var err error
|
||||
|
||||
// prevent users from creating too many swap requests
|
||||
if Delay.CheckUser(request.DeroAddr) {
|
||||
response.Error = "2 minutes wait time triggered"
|
||||
return
|
||||
}
|
||||
|
||||
// check if pair is enabled and available
|
||||
pair := request.Pair
|
||||
if !coin.IsPairEnabled(pair) || !IsPairAvailable[pair] {
|
||||
response.Error = fmt.Sprintf("%s swap currently not possible", pair)
|
||||
return
|
||||
}
|
||||
|
||||
response.Deposit = coin.RoundFloat(request.Amount, 5)
|
||||
|
||||
// create swap
|
||||
err = DeroXTCSwap(pair, request.DeroAddr, response.Deposit, &response)
|
||||
|
||||
if err != nil {
|
||||
response.Error = err.Error()
|
||||
log.Println(err)
|
||||
} else {
|
||||
Delay.AddUser(request.DeroAddr)
|
||||
}
|
||||
response.Request = request
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
func newUpgrader() *websocket.Upgrader {
|
||||
u := websocket.NewUpgrader()
|
||||
|
||||
u.CheckOrigin = (func(r *http.Request) bool {
|
||||
return true
|
||||
})
|
||||
|
||||
u.OnClose(func(c *websocket.Conn, err error) {
|
||||
WSConnections.Delete(c)
|
||||
})
|
||||
|
||||
u.OnMessage(func(c *websocket.Conn, messageType websocket.MessageType, data []byte) {
|
||||
|
||||
var in, out WS_Message
|
||||
var send bytes.Buffer
|
||||
|
||||
read := bytes.NewReader(data)
|
||||
decoder := json.NewDecoder(read)
|
||||
encoder := json.NewEncoder(&send)
|
||||
|
||||
err := decoder.Decode(&in)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
out.ID = in.ID
|
||||
|
||||
switch in.Method {
|
||||
case "swap":
|
||||
|
||||
var request coin.Swap_Request
|
||||
var response coin.Swap_Response
|
||||
|
||||
p := in.Params.(map[string]any)
|
||||
|
||||
request.Pair = p["pair"].(string)
|
||||
request.Amount = p["amount"].(float64)
|
||||
request.DeroAddr = p["dero_address"].(string)
|
||||
|
||||
switch request.Pair {
|
||||
case coin.BTCDERO, coin.LTCDERO, coin.ARRRDERO, coin.XMRDERO:
|
||||
response = Dero_Swap(request)
|
||||
case coin.DEROBTC, coin.DEROLTC, coin.DEROARRR, coin.DEROXMR:
|
||||
response = Reverse_Swap(request)
|
||||
default:
|
||||
}
|
||||
|
||||
out.Method = "swap"
|
||||
out.Result = response
|
||||
encoder.Encode(out)
|
||||
|
||||
SendWSData(c, send.Bytes())
|
||||
case "market":
|
||||
|
||||
mk.RLock()
|
||||
defer mk.RUnlock()
|
||||
|
||||
var data bytes.Buffer
|
||||
|
||||
out.Method = "market"
|
||||
inner := mk.Pairs
|
||||
out.Result = inner
|
||||
|
||||
encoder := json.NewEncoder(&data)
|
||||
encoder.Encode(out)
|
||||
|
||||
SendWSData(c, data.Bytes())
|
||||
|
||||
case "balance":
|
||||
|
||||
var data bytes.Buffer
|
||||
|
||||
out.Method = "balance"
|
||||
out.Result = UpdatePool()
|
||||
|
||||
encoder := json.NewEncoder(&data)
|
||||
encoder.Encode(out)
|
||||
|
||||
SendWSData(c, data.Bytes())
|
||||
|
||||
default:
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
func webSocketHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
upgrader := newUpgrader()
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
WSConnections.Store(conn, true)
|
||||
}
|
||||
|
||||
func SendWSData(c *websocket.Conn, data []byte) {
|
||||
|
||||
if len(data) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if c != nil {
|
||||
err := c.WriteMessage(websocket.TextMessage, data)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
} else {
|
||||
WSConnections.Range(func(k any, v any) bool {
|
||||
|
||||
conn := k.(*websocket.Conn)
|
||||
|
||||
err := conn.WriteMessage(websocket.TextMessage, data)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func StartServer() {
|
||||
|
||||
// cert files
|
||||
// Let's Encrypt:
|
||||
// certFile = fullchain.pem
|
||||
// keyFile = privkey.pem
|
||||
|
||||
// comment the following block to disable TLS
|
||||
cert, err := tls.LoadX509KeyPair("/etc/letsencrypt/live/mg25ot4aggt8dprv.myfritz.net/fullchain.pem", "/etc/letsencrypt/live/mg25ot4aggt8dprv.myfritz.net/privkey.pem")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/ws", webSocketHandler)
|
||||
|
||||
srv := nbhttp.NewServer(nbhttp.Config{
|
||||
Network: "tcp",
|
||||
Handler: mux,
|
||||
// comment the following 2 lines and uncomment "Addrs" to start server without TLS
|
||||
AddrsTLS: []string{cfg.Settings.ListenAddress},
|
||||
TLSConfig: &tls.Config{Certificates: []tls.Certificate{cert}},
|
||||
//Addrs: []string{cfg.Settings.ListenAddress},
|
||||
})
|
||||
|
||||
err = srv.Start()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
os.Exit(2)
|
||||
}
|
||||
srv.Wait()
|
||||
}
|
204
swap.go
Normal file
204
swap.go
Normal file
@ -0,0 +1,204 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"dero-swap/cfg"
|
||||
"dero-swap/coin"
|
||||
"dero-swap/dero"
|
||||
"dero-swap/monero"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func CreateSwap(pair string, wallet string, dest string, amount float64, price float64) int64 {
|
||||
|
||||
var entry coin.Swap_Entry
|
||||
var height uint64
|
||||
var payout float64 = amount
|
||||
|
||||
// get current block height. Ignore transactions < height
|
||||
switch pair {
|
||||
case coin.BTCDERO, coin.LTCDERO, coin.ARRRDERO:
|
||||
height = coin.XTCCheckBlockHeight(pair)
|
||||
case coin.XMRDERO:
|
||||
height = monero.GetHeight()
|
||||
default:
|
||||
height = dero.CheckBlockHeight()
|
||||
}
|
||||
|
||||
if height == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
entry.Coin = pair
|
||||
entry.Wallet = wallet
|
||||
entry.Destination = dest
|
||||
entry.Price = price
|
||||
entry.Amount = amount
|
||||
entry.Created = time.Now().UnixMilli()
|
||||
entry.Block = height
|
||||
entry.Status = 0
|
||||
|
||||
// create an integrated address for all Dero -> X swaps
|
||||
if pair == coin.DEROLTC || pair == coin.DEROBTC || pair == coin.DEROARRR || pair == coin.DEROXMR {
|
||||
if entry.Wallet = dero.MakeIntegratedAddress(entry.Created); entry.Wallet == "" {
|
||||
return 0
|
||||
}
|
||||
payout = entry.Price
|
||||
}
|
||||
|
||||
json_bytes, err := json.Marshal(&entry)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
err = os.WriteFile(fmt.Sprintf("swaps/active/%d", entry.Created), json_bytes, 0644)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
log.Printf("Swap request (%d) of %.8f (%s) successfully created\n", entry.Created, payout, entry.Coin)
|
||||
|
||||
return entry.Created
|
||||
}
|
||||
|
||||
// X to Dero Swaps
|
||||
func XTCSwap(pair string, dst_addr string, amount float64, resp *coin.Swap_Response) (err error) {
|
||||
|
||||
// check if Dero address is registered on chain
|
||||
if !dero.IsDeroAddressRegistered(dst_addr) {
|
||||
return fmt.Errorf("dero address is not registered")
|
||||
}
|
||||
|
||||
// check balance and include locked swap balance
|
||||
balance, err := dero.CheckWalletBalance()
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't check swap balance")
|
||||
}
|
||||
if coin.Locked.GetLockedBalance(pair)+amount+dero.TxFee > balance {
|
||||
return fmt.Errorf("insufficient swap balance")
|
||||
}
|
||||
|
||||
coin.Locked.AddLockedBalance(pair, amount)
|
||||
|
||||
// create/get a deposit address
|
||||
if pair != coin.XMRDERO {
|
||||
resp.Wallet = coin.XTCGetAddress(pair)
|
||||
} else {
|
||||
resp.Wallet = monero.MakeIntegratedAddress()
|
||||
}
|
||||
if resp.Wallet == "" {
|
||||
return fmt.Errorf("no swap deposit address available")
|
||||
}
|
||||
|
||||
var coin_value float64
|
||||
var atomicUnits uint = 8
|
||||
|
||||
switch pair {
|
||||
case coin.BTCDERO:
|
||||
coin_value = mk.Pairs.BTC
|
||||
case coin.LTCDERO:
|
||||
coin_value = mk.Pairs.LTC
|
||||
case coin.ARRRDERO:
|
||||
coin_value = mk.Pairs.ARRR
|
||||
case coin.XMRDERO:
|
||||
coin_value = mk.Pairs.XMR
|
||||
atomicUnits = 12
|
||||
}
|
||||
|
||||
deposit_value := coin_value * amount
|
||||
|
||||
var loops int = 5
|
||||
var isAvailable bool
|
||||
|
||||
// if there is a request with the same deposit amount, run in a loop and lower deposit value by 1 Sat
|
||||
for i := 0; i < loops; i++ {
|
||||
if coin.IsAmountFree(pair, deposit_value) {
|
||||
isAvailable = true
|
||||
break
|
||||
}
|
||||
deposit_value -= SATOSHI
|
||||
}
|
||||
if !isAvailable || deposit_value == 0 {
|
||||
return fmt.Errorf("Pre-Check: Couldn't create swap")
|
||||
}
|
||||
|
||||
deposit_value = coin.RoundFloat(deposit_value, atomicUnits)
|
||||
|
||||
resp.ID = CreateSwap(pair, resp.Wallet, dst_addr, amount, deposit_value)
|
||||
if resp.ID == 0 {
|
||||
return fmt.Errorf("couldn't create swap")
|
||||
}
|
||||
resp.Deposit = deposit_value
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Dero to X swaps
|
||||
func DeroXTCSwap(pair string, dst_addr string, amount float64, resp *coin.Swap_Response) (err error) {
|
||||
|
||||
var balance float64
|
||||
var atomicUnits uint = 8
|
||||
|
||||
// validate destination wallet and check for sufficient swap balance
|
||||
if pair != coin.DEROXMR {
|
||||
if !coin.XTCValidateAddress(pair, dst_addr) {
|
||||
return fmt.Errorf("%s address is not valid", pair[5:])
|
||||
}
|
||||
balance = coin.XTCGetBalance(pair)
|
||||
} else {
|
||||
if !monero.ValidateAddress(dst_addr) {
|
||||
return fmt.Errorf("XMR address is not valid")
|
||||
}
|
||||
balance = monero.GetBalance()
|
||||
atomicUnits = 12
|
||||
}
|
||||
|
||||
var coin_value float64
|
||||
var fees float64
|
||||
|
||||
// determine fees and current price
|
||||
switch pair {
|
||||
case coin.DEROLTC:
|
||||
coin_value = mk.Pairs.DEROLTC
|
||||
fees = cfg.SwapFees.Withdrawal.DeroLTC
|
||||
case coin.DEROARRR:
|
||||
coin_value = mk.Pairs.DEROARRR
|
||||
fees = cfg.SwapFees.Withdrawal.DeroARRR
|
||||
case coin.DEROBTC:
|
||||
coin_value = mk.Pairs.DEROBTC
|
||||
fees = cfg.SwapFees.Withdrawal.DeroBTC
|
||||
case coin.DEROXMR:
|
||||
coin_value = mk.Pairs.XMR
|
||||
fees = cfg.SwapFees.Withdrawal.DeroXMR
|
||||
}
|
||||
|
||||
payout_value := coin_value * amount
|
||||
if payout_value-fees < 0 {
|
||||
return fmt.Errorf("fees > payout value")
|
||||
}
|
||||
if payout_value == 0 {
|
||||
return fmt.Errorf("couldn't create swap")
|
||||
}
|
||||
|
||||
// check for reserved balance
|
||||
if coin.Locked.GetLockedBalance(pair)+payout_value+fees > balance {
|
||||
return fmt.Errorf("insufficient swap balance")
|
||||
}
|
||||
|
||||
coin.Locked.AddLockedBalance(pair, payout_value)
|
||||
|
||||
payout_value -= fees
|
||||
payout_value = coin.RoundFloat(payout_value, atomicUnits)
|
||||
resp.Swap = payout_value
|
||||
|
||||
resp.ID = CreateSwap(pair, "", dst_addr, amount, payout_value)
|
||||
if resp.ID == 0 {
|
||||
return fmt.Errorf("couldn't create swap")
|
||||
}
|
||||
resp.Wallet = dero.MakeIntegratedAddress(resp.ID)
|
||||
|
||||
return nil
|
||||
}
|
173
swap_manager.go
Normal file
173
swap_manager.go
Normal file
@ -0,0 +1,173 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"dero-swap/coin"
|
||||
"dero-swap/dero"
|
||||
"dero-swap/monero"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/deroproject/derohe/rpc"
|
||||
)
|
||||
|
||||
func Swap_Controller() {
|
||||
|
||||
var file_data []byte
|
||||
var swap_e coin.Swap_Entry
|
||||
var expired, fails, sent, active uint
|
||||
|
||||
var txs []rpc.Transfer
|
||||
var xmr_txs []monero.RPC_XMR_Transfer_Params
|
||||
|
||||
var dir_entries []fs.DirEntry
|
||||
var err error
|
||||
|
||||
for {
|
||||
|
||||
time.Sleep(time.Minute)
|
||||
dir_entries, err = os.ReadDir("swaps/active")
|
||||
|
||||
expired = 0
|
||||
fails = 0
|
||||
sent = 0
|
||||
active = 0
|
||||
txs = nil
|
||||
xmr_txs = nil
|
||||
|
||||
for _, e := range dir_entries {
|
||||
|
||||
active++
|
||||
file_data = nil
|
||||
err = nil
|
||||
|
||||
file_data, err = os.ReadFile("swaps/active/" + e.Name())
|
||||
if err != nil {
|
||||
fails++
|
||||
continue
|
||||
}
|
||||
err = json.Unmarshal(file_data, &swap_e)
|
||||
if err != nil {
|
||||
fails++
|
||||
continue
|
||||
}
|
||||
creation_t := time.UnixMilli(swap_e.Created)
|
||||
|
||||
// if there was no deposit, mark the request as expired
|
||||
if swap_e.Status == 0 && time.Since(creation_t) > time.Hour {
|
||||
os.WriteFile(fmt.Sprintf("swaps/expired/%d", swap_e.Created), file_data, 0644)
|
||||
os.Remove("swaps/active/" + e.Name())
|
||||
switch swap_e.Coin {
|
||||
case coin.LTCDERO, coin.BTCDERO, coin.ARRRDERO, coin.XMRDERO:
|
||||
coin.Locked.RemoveLockedBalance(swap_e.Coin, swap_e.Amount)
|
||||
default:
|
||||
coin.Locked.RemoveLockedBalance(swap_e.Coin, swap_e.Price)
|
||||
}
|
||||
|
||||
expired++
|
||||
continue
|
||||
}
|
||||
|
||||
var found_deposit, visible bool
|
||||
|
||||
// check for deposits
|
||||
switch swap_e.Coin {
|
||||
case coin.BTCDERO, coin.LTCDERO, coin.ARRRDERO:
|
||||
found_deposit, visible, _, err = coin.XTCListReceivedByAddress(swap_e.Coin, swap_e.Wallet, swap_e.Price, swap_e.Block, false)
|
||||
case coin.XMRDERO:
|
||||
if payment_id := monero.SplitIntegratedAddress(swap_e.Wallet); payment_id != "" {
|
||||
found_deposit = monero.XMRGetTX(payment_id, swap_e.Block)
|
||||
visible = found_deposit
|
||||
} else {
|
||||
log.Println("Can't split intragrated XMR address")
|
||||
}
|
||||
default:
|
||||
found_deposit = dero.CheckIncomingTransfers(uint64(swap_e.Created), swap_e.Block)
|
||||
visible = found_deposit
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Error checking incoming %s transactions\n", swap_e.Coin)
|
||||
fails++
|
||||
continue
|
||||
}
|
||||
|
||||
// mark request as done
|
||||
if swap_e.Status == 2 {
|
||||
err = os.WriteFile(fmt.Sprintf("swaps/done/%d", swap_e.Created), file_data, 0644)
|
||||
if err != nil {
|
||||
log.Printf("Can't mark swap as done, swap %d, err %v\n", swap_e.Created, err)
|
||||
} else {
|
||||
os.Remove("swaps/active/" + e.Name())
|
||||
}
|
||||
}
|
||||
|
||||
// start payout if there are at least 2 confirmations
|
||||
// requests won't be marked as expired, if there is already 1 confirmation
|
||||
if visible {
|
||||
if found_deposit && swap_e.Status <= 1 {
|
||||
// create transaction
|
||||
log.Printf("Found deposit for ID %d (%s): %.8f coins; adding to payout TX\n", swap_e.Created, swap_e.Coin, swap_e.Amount)
|
||||
|
||||
switch swap_e.Coin {
|
||||
case coin.DEROLTC, coin.DEROBTC:
|
||||
log.Println("Starting LTC/BTC payout")
|
||||
_, txid := coin.XTCSend(swap_e.Coin, swap_e.Destination, swap_e.Price)
|
||||
log.Printf("LTC TXID: %s\n", txid)
|
||||
coin.Locked.RemoveLockedBalance(swap_e.Coin, swap_e.Price)
|
||||
case coin.DEROARRR:
|
||||
log.Println("Starting ARRR payout")
|
||||
ok, result := coin.ARRR_Send(swap_e.Destination, swap_e.Price)
|
||||
log.Printf("ARRR status: %v, %s\n", ok, result)
|
||||
coin.Locked.RemoveLockedBalance(swap_e.Coin, swap_e.Price)
|
||||
case coin.DEROXMR:
|
||||
xmr_txs = append(xmr_txs, monero.AddTX(swap_e.Destination, swap_e.Price))
|
||||
default:
|
||||
txs = append(txs, dero.AddTX(swap_e.Destination, swap_e.Amount))
|
||||
}
|
||||
|
||||
swap_e.Status = 2
|
||||
sent++
|
||||
active--
|
||||
} else {
|
||||
// transaction was confirmed
|
||||
swap_e.Status = 1
|
||||
}
|
||||
|
||||
json_data, _ := json.Marshal(&swap_e)
|
||||
os.WriteFile("swaps/active/"+e.Name(), json_data, 0644)
|
||||
|
||||
if swap_e.Status == 2 {
|
||||
err = os.WriteFile(fmt.Sprintf("swaps/done/%d", swap_e.Created), file_data, 0644)
|
||||
if err != nil {
|
||||
log.Printf("Can't mark swap as done, swap %d, err %v\n", swap_e.Created, err)
|
||||
} else {
|
||||
os.Remove("swaps/active/" + e.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dero and Monero payout process
|
||||
if len(txs) > 0 {
|
||||
log.Println("Starting DERO payout process")
|
||||
dero.Payout(txs)
|
||||
}
|
||||
// TODO: create function and TX verification
|
||||
if len(xmr_txs) > 0 {
|
||||
log.Println("Starting XMR payout process")
|
||||
if ok, txid := monero.XMRSend(xmr_txs); ok {
|
||||
log.Printf("XMR transaction (TXID %s) successfully sent\n", txid)
|
||||
} else {
|
||||
log.Println("Error sending XMR transaction")
|
||||
}
|
||||
}
|
||||
|
||||
if sent+expired+fails > 0 {
|
||||
log.Printf("Swap processing: %d sent, %d expired, %d errors\n", sent, expired, fails)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user