satkas 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- satkas/__init__.py +30 -0
- satkas/__main__.py +4 -0
- satkas/cli/__init__.py +1 -0
- satkas/cli/cli.py +41 -0
- satkas/core/__init__.py +9 -0
- satkas/core/blib/__init__.py +0 -0
- satkas/core/blib/baddress.py +12 -0
- satkas/core/blib/brpc.py +225 -0
- satkas/core/blib/btx_size.py +4 -0
- satkas/core/blib/scripting.py +92 -0
- satkas/core/db/__init__.py +0 -0
- satkas/core/db/models.py +185 -0
- satkas/core/klib/__init__.py +0 -0
- satkas/core/klib/kaddress.py +38 -0
- satkas/core/klib/kbech32.py +181 -0
- satkas/core/klib/kdatatype.py +109 -0
- satkas/core/klib/kgrpc.py +138 -0
- satkas/core/klib/kopcodes.py +207 -0
- satkas/core/klib/ksign.py +293 -0
- satkas/core/klib/ktransactions.py +143 -0
- satkas/core/klib/ktx_mass.py +164 -0
- satkas/core/klib/messages_pb2.py +41 -0
- satkas/core/klib/messages_pb2_grpc.py +97 -0
- satkas/core/klib/rpc_pb2.py +366 -0
- satkas/core/klib/rpc_pb2_grpc.py +24 -0
- satkas/core/klib/schnorr_signature.py +243 -0
- satkas/core/klib/script_builder.py +90 -0
- satkas/core/klib/scripting.py +323 -0
- satkas/core/klib/serialization.py +194 -0
- satkas/core/p2p/__init__.py +0 -0
- satkas/core/p2p/maker_p2p_node.py +212 -0
- satkas/core/p2p/p2p_node.py +339 -0
- satkas/core/p2p/taker_p2p_node.py +88 -0
- satkas/core/services/__init__.py +4 -0
- satkas/core/services/base_service.py +212 -0
- satkas/core/services/bitcoind_service.py +813 -0
- satkas/core/services/bitcoind_wallet_service.py +356 -0
- satkas/core/services/external_btc_wallet_service.py +83 -0
- satkas/core/services/external_kaspawallet_service.py +103 -0
- satkas/core/services/external_ln_wallet_service.py +178 -0
- satkas/core/services/internal_kaspawallet_service.py +216 -0
- satkas/core/services/kaspa_explorer_service.py +583 -0
- satkas/core/services/kaspad_service.py +620 -0
- satkas/core/services/kaspawallet_service.py +309 -0
- satkas/core/services/lnbits_service.py +329 -0
- satkas/core/services/lncli_service.py +438 -0
- satkas/core/services/mempool_service.py +581 -0
- satkas/core/services/rustykaspawallet_service.py +451 -0
- satkas/core/services/service_manager.py +602 -0
- satkas/core/services/tor_service.py +126 -0
- satkas/core/swapper/__init__.py +0 -0
- satkas/core/swapper/atomic_swap.py +370 -0
- satkas/core/swapper/counterparty.py +1618 -0
- satkas/core/swapper/maker.py +1403 -0
- satkas/core/swapper/swap_errors.py +71 -0
- satkas/core/swapper/swap_events.py +68 -0
- satkas/core/swapper/swap_interaction.py +121 -0
- satkas/core/swapper/taker.py +2163 -0
- satkas/main.py +293 -0
- satkas/main_app.kv +110 -0
- satkas/ui/__init__.py +0 -0
- satkas/ui/media/__init__.py +10 -0
- satkas/ui/media/bitcoin_logo.png +0 -0
- satkas/ui/media/kaspa-icon-green.png +0 -0
- satkas/ui/screens/__init__.py +0 -0
- satkas/ui/screens/base_responsive_layout/__init__.py +19 -0
- satkas/ui/screens/base_responsive_layout/base_responsive_layout.py +57 -0
- satkas/ui/screens/base_responsive_layout/common/__init__.py +7 -0
- satkas/ui/screens/base_responsive_layout/desktop/__init__.py +9 -0
- satkas/ui/screens/base_responsive_layout/desktop/desktop_layout.kv +21 -0
- satkas/ui/screens/base_responsive_layout/desktop/desktop_layout.py +81 -0
- satkas/ui/screens/base_responsive_layout/mobile/__init__.py +9 -0
- satkas/ui/screens/base_responsive_layout/mobile/mobile_layout.kv +18 -0
- satkas/ui/screens/base_responsive_layout/mobile/mobile_layout.py +36 -0
- satkas/ui/screens/base_responsive_layout/tablet/__init__.py +9 -0
- satkas/ui/screens/base_responsive_layout/tablet/tablet_layout.kv +22 -0
- satkas/ui/screens/base_responsive_layout/tablet/tablet_layout.py +37 -0
- satkas/ui/screens/dashboard_screen/__init__.py +4 -0
- satkas/ui/screens/dashboard_screen/dashboard_screen.kv +144 -0
- satkas/ui/screens/dashboard_screen/dashboard_screen.py +274 -0
- satkas/ui/screens/quick_swap_v2/__init__.py +8 -0
- satkas/ui/screens/quick_swap_v2/components/__init__.py +21 -0
- satkas/ui/screens/quick_swap_v2/components/attention_pulse.py +36 -0
- satkas/ui/screens/quick_swap_v2/components/contract_field.kv +86 -0
- satkas/ui/screens/quick_swap_v2/components/contract_field.py +68 -0
- satkas/ui/screens/quick_swap_v2/components/invoice_field.kv +103 -0
- satkas/ui/screens/quick_swap_v2/components/invoice_field.py +353 -0
- satkas/ui/screens/quick_swap_v2/components/payment_field.kv +73 -0
- satkas/ui/screens/quick_swap_v2/components/payment_field.py +112 -0
- satkas/ui/screens/quick_swap_v2/components/payout_bar.kv +137 -0
- satkas/ui/screens/quick_swap_v2/components/payout_bar.py +396 -0
- satkas/ui/screens/quick_swap_v2/components/peer_selector.kv +53 -0
- satkas/ui/screens/quick_swap_v2/components/peer_selector.py +367 -0
- satkas/ui/screens/quick_swap_v2/components/status_widget.kv +102 -0
- satkas/ui/screens/quick_swap_v2/components/status_widget.py +101 -0
- satkas/ui/screens/quick_swap_v2/components/swap_editor.kv +189 -0
- satkas/ui/screens/quick_swap_v2/components/swap_editor.py +471 -0
- satkas/ui/screens/quick_swap_v2/kivy_interaction.py +107 -0
- satkas/ui/screens/quick_swap_v2/quick_swap_v2_screen.kv +210 -0
- satkas/ui/screens/quick_swap_v2/quick_swap_v2_screen.py +1657 -0
- satkas/ui/screens/quick_swap_v2/swap_controller.py +423 -0
- satkas/ui/screens/setup_wizard/__init__.py +3 -0
- satkas/ui/screens/setup_wizard/pages/__init__.py +19 -0
- satkas/ui/screens/setup_wizard/pages/bitcoin_setup_page.kv +246 -0
- satkas/ui/screens/setup_wizard/pages/bitcoin_setup_page.py +152 -0
- satkas/ui/screens/setup_wizard/pages/btc_wallet_setup_page.kv +222 -0
- satkas/ui/screens/setup_wizard/pages/btc_wallet_setup_page.py +147 -0
- satkas/ui/screens/setup_wizard/pages/kaspa_wallet_setup_page.kv +315 -0
- satkas/ui/screens/setup_wizard/pages/kaspa_wallet_setup_page.py +227 -0
- satkas/ui/screens/setup_wizard/pages/kaspad_setup_page.kv +230 -0
- satkas/ui/screens/setup_wizard/pages/kaspad_setup_page.py +212 -0
- satkas/ui/screens/setup_wizard/pages/landing_page.kv +61 -0
- satkas/ui/screens/setup_wizard/pages/landing_page.py +26 -0
- satkas/ui/screens/setup_wizard/pages/ln_wallet_setup_page.kv +332 -0
- satkas/ui/screens/setup_wizard/pages/ln_wallet_setup_page.py +312 -0
- satkas/ui/screens/setup_wizard/pages/password_setup_page.kv +120 -0
- satkas/ui/screens/setup_wizard/pages/password_setup_page.py +70 -0
- satkas/ui/screens/setup_wizard/pages/tor_setup_page.kv +130 -0
- satkas/ui/screens/setup_wizard/pages/tor_setup_page.py +110 -0
- satkas/ui/screens/setup_wizard/setup_wizard.kv +39 -0
- satkas/ui/screens/setup_wizard/setup_wizard.py +227 -0
- satkas/ui/utils/__init__.py +5 -0
- satkas/ui/utils/qr_utils.py +51 -0
- satkas-0.1.0.dist-info/METADATA +111 -0
- satkas-0.1.0.dist-info/RECORD +129 -0
- satkas-0.1.0.dist-info/WHEEL +5 -0
- satkas-0.1.0.dist-info/entry_points.txt +2 -0
- satkas-0.1.0.dist-info/licenses/LICENSE +165 -0
- satkas-0.1.0.dist-info/top_level.txt +1 -0
satkas/__init__.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
|
|
2
|
+
__name__ = 'satkas'
|
|
3
|
+
__version__ = '0.1.0'
|
|
4
|
+
__author__ = 'LordStapy'
|
|
5
|
+
__license__ = 'LGPLv3'
|
|
6
|
+
__url__ = 'https://github.com/LordStapy/satkas'
|
|
7
|
+
__description__ = 'Bitcoin-Kaspa Atomic Swap Utility'
|
|
8
|
+
__keywords__ = 'btc kas atomic-swap kivy p2p'
|
|
9
|
+
__classifiers__ = [
|
|
10
|
+
'Development Status :: 3 - Alpha',
|
|
11
|
+
'Intended Audience :: Developers',
|
|
12
|
+
'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)',
|
|
13
|
+
'Programming Language :: Python :: 3',
|
|
14
|
+
'Programming Language :: Python :: 3 :: Only',
|
|
15
|
+
'Programming Language :: Python :: 3.10',
|
|
16
|
+
]
|
|
17
|
+
__all__ = [
|
|
18
|
+
'main',
|
|
19
|
+
'core',
|
|
20
|
+
'ui',
|
|
21
|
+
'cli',
|
|
22
|
+
__name__,
|
|
23
|
+
__version__,
|
|
24
|
+
__author__,
|
|
25
|
+
__license__,
|
|
26
|
+
__url__,
|
|
27
|
+
__description__,
|
|
28
|
+
__keywords__,
|
|
29
|
+
__classifiers__
|
|
30
|
+
]
|
satkas/__main__.py
ADDED
satkas/cli/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .cli import main
|
satkas/cli/cli.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# satkas/cli/cli.py
|
|
2
|
+
"""Console entry point for the SatKas GUI.
|
|
3
|
+
|
|
4
|
+
Supports:
|
|
5
|
+
satkas
|
|
6
|
+
satkas --peer <onion>
|
|
7
|
+
MAKER_ENDPOINT=<onion> satkas
|
|
8
|
+
"""
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
# Before logging.basicConfig and before importing satkas.main / Kivy.
|
|
13
|
+
os.environ["KIVY_NO_CONSOLELOG"] = "1"
|
|
14
|
+
os.environ["KCFG_KIVY_LOG_LEVEL"] = "error"
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import logging
|
|
18
|
+
|
|
19
|
+
logging.basicConfig(level=logging.INFO)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def main():
|
|
23
|
+
# Must run before importing satkas.main — Kivy parses sys.argv at
|
|
24
|
+
# import time and rejects unknown options like --peer.
|
|
25
|
+
parser = argparse.ArgumentParser(prog="satkas")
|
|
26
|
+
parser.add_argument(
|
|
27
|
+
"--peer",
|
|
28
|
+
default=None,
|
|
29
|
+
help="Force a maker onion endpoint (sets MAKER_ENDPOINT)",
|
|
30
|
+
)
|
|
31
|
+
args, remaining = parser.parse_known_args()
|
|
32
|
+
if args.peer:
|
|
33
|
+
os.environ["MAKER_ENDPOINT"] = args.peer
|
|
34
|
+
sys.argv = [sys.argv[0]] + remaining
|
|
35
|
+
|
|
36
|
+
from satkas.main import main as satkas_main
|
|
37
|
+
satkas_main()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if __name__ == "__main__":
|
|
41
|
+
main()
|
satkas/core/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from bitcoinutils.keys import PublicKey
|
|
5
|
+
from bitcoinutils.setup import setup as btc_network_setup
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def p2wpkh_address(pubkey):
|
|
9
|
+
assert len(pubkey) == 33 # only compressed keys for P2WPKH
|
|
10
|
+
btc_network_setup(os.getenv('BTC_NETWORK', 'mainnet'))
|
|
11
|
+
pubkey = PublicKey(hex_str=pubkey.hex())
|
|
12
|
+
return pubkey.get_segwit_address()
|
satkas/core/blib/brpc.py
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import datetime
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import json
|
|
6
|
+
import aiohttp
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger('brpc')
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BrpcError(Exception):
|
|
12
|
+
"""Raised when a bitcoind JSON-RPC call returns an error object."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, error):
|
|
15
|
+
self.error = error
|
|
16
|
+
if isinstance(error, dict):
|
|
17
|
+
message = error.get('message', str(error))
|
|
18
|
+
code = error.get('code')
|
|
19
|
+
super().__init__(f"RPC error {code}: {message}" if code is not None else message)
|
|
20
|
+
else:
|
|
21
|
+
super().__init__(str(error))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def raise_for_error(res):
|
|
25
|
+
"""Raise BrpcError if the RPC response contains an error. Returns res unchanged otherwise."""
|
|
26
|
+
if res is None:
|
|
27
|
+
raise BrpcError({'message': 'empty RPC response'})
|
|
28
|
+
error = res.get('error')
|
|
29
|
+
if error is not None:
|
|
30
|
+
raise BrpcError(error)
|
|
31
|
+
return res
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async def base_req(method, *args, **kwargs):
|
|
35
|
+
server = kwargs.pop('server', None)
|
|
36
|
+
if server is None:
|
|
37
|
+
host = os.getenv('BITCOIND_HOST', '127.0.0.1')
|
|
38
|
+
port = os.getenv('BITCOIND_PORT', '8332')
|
|
39
|
+
server = f"{host}:{port}"
|
|
40
|
+
user = kwargs.pop('user', None)
|
|
41
|
+
if user is None:
|
|
42
|
+
user = os.getenv('BTC_RPC_USER', '')
|
|
43
|
+
password = kwargs.pop('password', None)
|
|
44
|
+
if password is None:
|
|
45
|
+
password = os.getenv('BTC_RPC_PASS', '')
|
|
46
|
+
|
|
47
|
+
endpoint = f"http://{server}"
|
|
48
|
+
|
|
49
|
+
wallet = kwargs.pop('rpcwallet', None)
|
|
50
|
+
if wallet is not None:
|
|
51
|
+
endpoint += f"/wallet/{wallet}"
|
|
52
|
+
verbose = kwargs.pop('verbose_print', False)
|
|
53
|
+
|
|
54
|
+
payload = {
|
|
55
|
+
'jsonrpc': '2.0',
|
|
56
|
+
'id': '0',
|
|
57
|
+
'method': method,
|
|
58
|
+
'params': list(args) if args else kwargs
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
headers = {
|
|
62
|
+
'Content-Type': 'application/json'
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
auth = aiohttp.BasicAuth(user, password)
|
|
66
|
+
|
|
67
|
+
async with aiohttp.ClientSession() as session:
|
|
68
|
+
async with session.post(endpoint, auth=auth, data=json.dumps(payload), headers=headers) as response:
|
|
69
|
+
res = await response.json()
|
|
70
|
+
if verbose:
|
|
71
|
+
logger.debug(res)
|
|
72
|
+
return res
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async def createwallet(
|
|
76
|
+
name,
|
|
77
|
+
disable_private_keys=True,
|
|
78
|
+
blank=False,
|
|
79
|
+
passphrase='',
|
|
80
|
+
avoid_reuse=False,
|
|
81
|
+
descriptors=True,
|
|
82
|
+
load_on_startup=True,
|
|
83
|
+
**kwargs
|
|
84
|
+
):
|
|
85
|
+
return await base_req(
|
|
86
|
+
'createwallet',
|
|
87
|
+
name,
|
|
88
|
+
disable_private_keys,
|
|
89
|
+
blank,
|
|
90
|
+
passphrase,
|
|
91
|
+
avoid_reuse,
|
|
92
|
+
descriptors,
|
|
93
|
+
load_on_startup,
|
|
94
|
+
**kwargs
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
async def loadwallet(name, load_on_startup=True, **kwargs):
|
|
99
|
+
return await base_req('loadwallet', name, load_on_startup, **kwargs)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
async def listwallets(**kwargs):
|
|
103
|
+
return await base_req('listwallets', **kwargs)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
async def getwalletinfo(**kwargs):
|
|
107
|
+
return await base_req('getwalletinfo', **kwargs)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
async def walletpassphrase(passphrase, timeout=60, **kwargs):
|
|
111
|
+
return await base_req('walletpassphrase', passphrase, timeout, **kwargs)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
async def walletlock(**kwargs):
|
|
115
|
+
return await base_req('walletlock', **kwargs)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
async def getbalance(*args, **kwargs):
|
|
119
|
+
return await base_req('getbalance', *args, **kwargs)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
async def getnewaddress(label='', address_type=None, **kwargs):
|
|
123
|
+
if address_type is not None:
|
|
124
|
+
return await base_req('getnewaddress', label, address_type, **kwargs)
|
|
125
|
+
return await base_req('getnewaddress', label, **kwargs)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
async def scantxoutset(action, descriptors):
|
|
129
|
+
return await base_req('scantxoutset', action, descriptors)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
async def importdescriptors(descriptors, ts=None, **kwargs):
|
|
133
|
+
if not isinstance(descriptors, list):
|
|
134
|
+
descriptors = [descriptors]
|
|
135
|
+
if ts is None:
|
|
136
|
+
ts = int((datetime.datetime.now() - datetime.timedelta(hours=1)).timestamp())
|
|
137
|
+
payload = [
|
|
138
|
+
{
|
|
139
|
+
'desc': desc,
|
|
140
|
+
'timestamp': ts
|
|
141
|
+
}
|
|
142
|
+
for desc in descriptors
|
|
143
|
+
]
|
|
144
|
+
return await base_req('importdescriptors', payload, **kwargs)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
async def listunspent(addresses=None, minconf=1, maxconf=9999999, include_unsafe=True, query_options=None, **kwargs):
|
|
148
|
+
if addresses is None:
|
|
149
|
+
addresses = []
|
|
150
|
+
if not isinstance(addresses, list):
|
|
151
|
+
addresses = [addresses]
|
|
152
|
+
if query_options is None:
|
|
153
|
+
query_options = {}
|
|
154
|
+
payload = (minconf, maxconf, addresses, include_unsafe, query_options)
|
|
155
|
+
return await base_req('listunspent', *payload, **kwargs)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
async def getdescriptorinfo(descriptor, **kwargs):
|
|
159
|
+
return await base_req('getdescriptorinfo', descriptor, **kwargs)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
async def deriveaddresses(descriptor, **kwargs):
|
|
163
|
+
return await base_req('deriveaddresses', descriptor, **kwargs)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
async def getaddressinfo(address, **kwargs):
|
|
167
|
+
return await base_req('getaddressinfo', address, **kwargs)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
async def listtransactions(label=None, count=10, skip=0, include_watchonly=True, **kwargs):
|
|
171
|
+
if label is None:
|
|
172
|
+
label = '*'
|
|
173
|
+
return await base_req('listtransactions', label, count, skip, include_watchonly, **kwargs)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
async def gettransaction(txid, include_watchonly=True, verbose=True, **kwargs):
|
|
177
|
+
return await base_req('gettransaction', txid, include_watchonly, verbose, **kwargs)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
async def getrawtransaction(txid, verbose=True, blockhash=None, **kwargs):
|
|
181
|
+
if blockhash is not None:
|
|
182
|
+
return await base_req('getrawtransaction', txid, verbose, blockhash, **kwargs)
|
|
183
|
+
return await base_req('getrawtransaction', txid, verbose, **kwargs)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
async def gettxout(txid, vout, include_mempool=True, **kwargs):
|
|
187
|
+
return await base_req('gettxout', txid, vout, include_mempool, **kwargs)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
async def gettxspendingprevout(outputs, **kwargs):
|
|
191
|
+
"""
|
|
192
|
+
Scan mempool (and confirmed chain if -txspenderindex) for txs spending the given outputs.
|
|
193
|
+
outputs: list of {"txid": "...", "vout": n}
|
|
194
|
+
"""
|
|
195
|
+
return await base_req('gettxspendingprevout', outputs, **kwargs)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
async def getblockchaininfo(**kwargs):
|
|
199
|
+
return await base_req('getblockchaininfo', **kwargs)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
async def sendtoaddress(address, amount, *args, **kwargs):
|
|
203
|
+
# TODO: manage encrypted wallets, need to call walletpassphrase before submitting this command
|
|
204
|
+
return await base_req('sendtoaddress', address, amount, *args, **kwargs)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
async def generatetoaddress(address, nblocks=1, **kwargs):
|
|
208
|
+
return await base_req('generatetoaddress', nblocks, address, **kwargs)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
async def sendrawtransaction(raw_transaction, **kwargs):
|
|
212
|
+
res = await base_req('sendrawtransaction', raw_transaction, **kwargs)
|
|
213
|
+
if res.get('error'):
|
|
214
|
+
logger.error(f"sendrawtransaction failed: {res['error']}")
|
|
215
|
+
else:
|
|
216
|
+
logger.info(f"sendrawtransaction succeeded: txid={res.get('result')}")
|
|
217
|
+
return res
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
async def getblockcount(**kwargs):
|
|
221
|
+
return await base_req('getblockcount', **kwargs)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
async def estimatesmartfee(conf_target, estimate_mode='CONSERVATIVE', **kwargs):
|
|
225
|
+
return await base_req('estimatesmartfee', conf_target, estimate_mode, **kwargs)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
|
|
2
|
+
import struct
|
|
3
|
+
|
|
4
|
+
from bitcoinutils.script import Script
|
|
5
|
+
|
|
6
|
+
# define few opcodes
|
|
7
|
+
OP_IF = b'\x63'
|
|
8
|
+
OP_SIZE = b'\x82'
|
|
9
|
+
OP_32 = b'\x20'
|
|
10
|
+
OP_EQUAL_VERIFY = b'\x88'
|
|
11
|
+
OP_SHA256 = b'\xa8'
|
|
12
|
+
OP_DUP = b'\x76'
|
|
13
|
+
OP_HASH160 = b'\xa9'
|
|
14
|
+
OP_ELSE = b'\x67'
|
|
15
|
+
OP_CHECK_LOCK_TIME_VERIFY = b'\xb1'
|
|
16
|
+
OP_DROP = b'\x75'
|
|
17
|
+
OP_ENDIF = b'\x68'
|
|
18
|
+
OP_CHECK_SIG = b'\xac'
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def build_contract_descriptor(sender, receiver, timelock, secret_hash):
|
|
22
|
+
# not working, can't use pkh without the actual key, keyhash alone is not enough
|
|
23
|
+
# even addr is bad, problem is probably the or_i block entirely
|
|
24
|
+
sender = f"addr({sender})"
|
|
25
|
+
receiver = f"addr({receiver})"
|
|
26
|
+
redeem_path = f"and_v(sha256({secret_hash.hex()}),{receiver})"
|
|
27
|
+
refund_path = f"and_v(after({timelock}),{sender})"
|
|
28
|
+
descriptor = f"wsh(or_i({redeem_path},{refund_path}))"
|
|
29
|
+
return descriptor
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def build_btc_contract_script_old(sender_pkh, receiver_pkh, locktime, secret_hash):
|
|
33
|
+
script = b''
|
|
34
|
+
script += OP_IF
|
|
35
|
+
script += OP_SIZE
|
|
36
|
+
script += b'\x01'
|
|
37
|
+
script += OP_32
|
|
38
|
+
script += OP_EQUAL_VERIFY
|
|
39
|
+
script += OP_SHA256
|
|
40
|
+
script += b'\x20'
|
|
41
|
+
script += secret_hash
|
|
42
|
+
script += OP_EQUAL_VERIFY
|
|
43
|
+
script += OP_DUP
|
|
44
|
+
script += OP_HASH160
|
|
45
|
+
script += b'\x14'
|
|
46
|
+
script += receiver_pkh
|
|
47
|
+
script += OP_ELSE
|
|
48
|
+
script += b'\x02' # <- hardcoded locktime len, working with test values, but will break
|
|
49
|
+
script += struct.pack('<h', locktime)
|
|
50
|
+
script += OP_CHECK_LOCK_TIME_VERIFY
|
|
51
|
+
script += OP_DROP
|
|
52
|
+
script += OP_DUP
|
|
53
|
+
script += OP_HASH160
|
|
54
|
+
script += b'\x14'
|
|
55
|
+
script += sender_pkh
|
|
56
|
+
script += OP_ENDIF
|
|
57
|
+
script += OP_EQUAL_VERIFY
|
|
58
|
+
script += OP_CHECK_SIG
|
|
59
|
+
return script
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def build_btc_contract_script(sender_pkh, receiver_pkh, locktime, secret_hash):
|
|
63
|
+
redeem_script = Script(
|
|
64
|
+
[
|
|
65
|
+
"OP_IF",
|
|
66
|
+
"OP_SIZE", 0X20, "OP_EQUALVERIFY",
|
|
67
|
+
"OP_SHA256", secret_hash.hex(), "OP_EQUALVERIFY",
|
|
68
|
+
"OP_DUP", "OP_HASH160", receiver_pkh,
|
|
69
|
+
"OP_ELSE",
|
|
70
|
+
locktime, "OP_CHECKLOCKTIMEVERIFY", "OP_DROP",
|
|
71
|
+
"OP_DUP", "OP_HASH160", sender_pkh,
|
|
72
|
+
"OP_ENDIF",
|
|
73
|
+
"OP_EQUALVERIFY",
|
|
74
|
+
"OP_CHECKSIG"
|
|
75
|
+
]
|
|
76
|
+
)
|
|
77
|
+
return redeem_script
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def build_btc_spend_script(signature, pubkey, contract, refund=False, secret=None):
|
|
81
|
+
payload = list()
|
|
82
|
+
payload.append(signature)
|
|
83
|
+
payload.append(pubkey.to_hex())
|
|
84
|
+
if refund:
|
|
85
|
+
payload.append('')
|
|
86
|
+
else:
|
|
87
|
+
if isinstance(secret, bytes):
|
|
88
|
+
secret = secret.hex()
|
|
89
|
+
payload.append(secret)
|
|
90
|
+
payload.append('01')
|
|
91
|
+
payload.append(contract)
|
|
92
|
+
return payload
|
|
File without changes
|
satkas/core/db/models.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
|
|
2
|
+
import datetime
|
|
3
|
+
import os
|
|
4
|
+
import logging
|
|
5
|
+
import sys
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
from peewee import DateTimeField, Model, CharField, BooleanField, IntegerField, TextField
|
|
9
|
+
from playhouse.migrate import SqliteDatabase
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def resource_path(relative_path):
|
|
13
|
+
# this is used to get the path to the database file, when the app is packaged with Pyinstaller (or other bundlers, maybe?)
|
|
14
|
+
try:
|
|
15
|
+
base_path = sys._MEIPASS
|
|
16
|
+
except Exception:
|
|
17
|
+
base_path = os.path.abspath(os.path.dirname(__file__))
|
|
18
|
+
return os.path.join(base_path, relative_path)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# set peewee logger to warning
|
|
22
|
+
logging.getLogger('peewee').setLevel(logging.WARNING)
|
|
23
|
+
db_path = os.getenv('SATKAS_DB_PATH', None)
|
|
24
|
+
if db_path is None:
|
|
25
|
+
db_path = resource_path('satkas.db')
|
|
26
|
+
db = SqliteDatabase(db_path, timeout=10)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class BaseModel(Model):
|
|
30
|
+
class Meta:
|
|
31
|
+
database = db
|
|
32
|
+
|
|
33
|
+
created_at = DateTimeField(default=datetime.datetime.now)
|
|
34
|
+
updated_at = DateTimeField(default=datetime.datetime.now)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Swap(BaseModel):
|
|
38
|
+
swap_type = CharField(default='')
|
|
39
|
+
# Indexed because a swap's key index is its row's position among its own
|
|
40
|
+
# side's rows, so every derivation counts rows filtered by this.
|
|
41
|
+
side = CharField(default='', index=True) # maker / taker
|
|
42
|
+
remote_pubkey = CharField(default='')
|
|
43
|
+
ln_invoice = CharField(default=None, unique=True, null=True)
|
|
44
|
+
payment_hash = CharField(default='')
|
|
45
|
+
secret_hash = CharField(default='')
|
|
46
|
+
sender_address = CharField(default='')
|
|
47
|
+
receiver_address = CharField(default='')
|
|
48
|
+
btc_sender_address = CharField(default='', null=True)
|
|
49
|
+
btc_receiver_address = CharField(default='', null=True)
|
|
50
|
+
contract = CharField(default='')
|
|
51
|
+
btc_contract = CharField(default='', null=True)
|
|
52
|
+
p2sh_address = CharField(default='')
|
|
53
|
+
btc_p2sh_address = CharField(default='', null=True)
|
|
54
|
+
dwork_amount = IntegerField(default=0)
|
|
55
|
+
sat_amount = IntegerField(default=0)
|
|
56
|
+
kas_locktime = IntegerField(null=True) # daa score (on-chain) or ms timestamp (LN)
|
|
57
|
+
btc_locktime = IntegerField(null=True) # bitcoin block height
|
|
58
|
+
dag_checkpoint_hash = CharField(null=True) # virtual-chain start hash at OPENED
|
|
59
|
+
# INIT → OPENED → FUNDED → COMPLETING|REFUNDING → COMPLETED|REFUNDED
|
|
60
|
+
# ↘ EXPIRED | FAILED
|
|
61
|
+
# OPENED = contracts ready, our pay not yet submitted.
|
|
62
|
+
# FUNDED = our on-chain fund or LN pay submitted (capital at risk).
|
|
63
|
+
status = CharField(default='INIT')
|
|
64
|
+
txid = CharField(default=None, null=True)
|
|
65
|
+
btc_txid = CharField(default=None, null=True)
|
|
66
|
+
secret = CharField(default=None, null=True)
|
|
67
|
+
output_address = CharField(null=True) # kas redeem/refund destination
|
|
68
|
+
btc_output_address = CharField(null=True)
|
|
69
|
+
extra_info = TextField(null=True) # JSON object for non-core / plugin data
|
|
70
|
+
|
|
71
|
+
def get_extra(self):
|
|
72
|
+
if not self.extra_info:
|
|
73
|
+
return {}
|
|
74
|
+
try:
|
|
75
|
+
return json.loads(self.extra_info)
|
|
76
|
+
except (TypeError, ValueError):
|
|
77
|
+
return {}
|
|
78
|
+
|
|
79
|
+
def set_extra(self, key, value):
|
|
80
|
+
extra = self.get_extra()
|
|
81
|
+
extra[key] = value
|
|
82
|
+
self.extra_info = json.dumps(extra)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class WalletModel(BaseModel):
|
|
86
|
+
mnemonic = CharField()
|
|
87
|
+
is_encrypted = BooleanField(default=False)
|
|
88
|
+
address_counter = IntegerField(default=0)
|
|
89
|
+
next_address = CharField()
|
|
90
|
+
# next_btc_address = CharField(default=None, null=True)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class MakerWallet(WalletModel):
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class TakerWallet(WalletModel):
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# not using p2p table yet
|
|
102
|
+
class P2PNode(BaseModel):
|
|
103
|
+
pubkey = CharField()
|
|
104
|
+
p2p_endpoint = CharField()
|
|
105
|
+
swap_endpoint = CharField()
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class Setting(BaseModel):
|
|
109
|
+
"""Application settings table for storing all app configuration."""
|
|
110
|
+
name = CharField(unique=True, index=True) # e.g., "service.kaspad.host"
|
|
111
|
+
value = CharField(null=True) # Store as string, cast when retrieving
|
|
112
|
+
value_type = CharField(default='str') # 'str', 'int', 'bool', 'json', 'float'
|
|
113
|
+
|
|
114
|
+
def get_typed_value(self):
|
|
115
|
+
"""Return the value cast to the appropriate Python type."""
|
|
116
|
+
if self.value is None:
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
if self.value_type == 'int':
|
|
120
|
+
return int(self.value)
|
|
121
|
+
elif self.value_type == 'bool':
|
|
122
|
+
return self.value.lower() in ('true', '1', 'yes', 'on')
|
|
123
|
+
elif self.value_type == 'float':
|
|
124
|
+
return float(self.value)
|
|
125
|
+
elif self.value_type == 'json':
|
|
126
|
+
import json
|
|
127
|
+
return json.loads(self.value)
|
|
128
|
+
else: # 'str' or unknown
|
|
129
|
+
return self.value
|
|
130
|
+
|
|
131
|
+
@classmethod
|
|
132
|
+
def set_value(cls, name, value, value_type='str'):
|
|
133
|
+
"""Set or update a setting value."""
|
|
134
|
+
# Convert value to string for storage
|
|
135
|
+
if value is None:
|
|
136
|
+
str_value = None
|
|
137
|
+
elif value_type == 'bool':
|
|
138
|
+
str_value = 'true' if value else 'false'
|
|
139
|
+
elif value_type == 'json':
|
|
140
|
+
str_value = json.dumps(value)
|
|
141
|
+
else:
|
|
142
|
+
str_value = str(value)
|
|
143
|
+
|
|
144
|
+
# Upsert the setting
|
|
145
|
+
setting, created = cls.get_or_create(
|
|
146
|
+
name=name,
|
|
147
|
+
defaults={
|
|
148
|
+
'value': str_value,
|
|
149
|
+
'value_type': value_type
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
if not created:
|
|
154
|
+
setting.value = str_value
|
|
155
|
+
setting.value_type = value_type
|
|
156
|
+
setting.save()
|
|
157
|
+
|
|
158
|
+
return setting
|
|
159
|
+
|
|
160
|
+
@classmethod
|
|
161
|
+
def get_value(cls, name, default=None):
|
|
162
|
+
"""Get a setting value with optional default."""
|
|
163
|
+
try:
|
|
164
|
+
setting = cls.get(cls.name == name)
|
|
165
|
+
return setting.get_typed_value()
|
|
166
|
+
except cls.DoesNotExist:
|
|
167
|
+
return default
|
|
168
|
+
|
|
169
|
+
@classmethod
|
|
170
|
+
def get_all_settings(cls):
|
|
171
|
+
"""Get all settings as a dictionary."""
|
|
172
|
+
settings = {}
|
|
173
|
+
for setting in cls.select():
|
|
174
|
+
settings[setting.name] = setting.get_typed_value()
|
|
175
|
+
return settings
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def initialize_db():
|
|
179
|
+
db.connect()
|
|
180
|
+
db.create_tables([
|
|
181
|
+
Swap, MakerWallet, TakerWallet, Setting
|
|
182
|
+
], safe=True)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
initialize_db()
|
|
File without changes
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
|
|
2
|
+
import os
|
|
3
|
+
import hashlib
|
|
4
|
+
|
|
5
|
+
from satkas.core.klib.kbech32 import encode_address, decode_address
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_script_hash(script):
|
|
10
|
+
script_hash = hashlib.blake2b(script, digest_size=32).digest()
|
|
11
|
+
return script_hash
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def p2sh_address_from_script_hash(script_hash):
|
|
15
|
+
if isinstance(script_hash, bytes):
|
|
16
|
+
script_hash = [i for i in script_hash]
|
|
17
|
+
version = 8
|
|
18
|
+
return encode_address(os.getenv('KAS_NETWORK_PREFIX', 'kaspa'), script_hash, version)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def p2sh_address_from_script(script):
|
|
22
|
+
if isinstance(script, list):
|
|
23
|
+
script = bytes(script)
|
|
24
|
+
script_hash = get_script_hash(script)
|
|
25
|
+
return p2sh_address_from_script_hash(script_hash)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_pubkey_hash(pubkey):
|
|
29
|
+
pubkey_hash = hashlib.blake2b(pubkey, digest_size=32).digest()
|
|
30
|
+
return pubkey_hash
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def p2pk_address(pubkey):
|
|
34
|
+
if isinstance(pubkey, bytes):
|
|
35
|
+
pubkey = [i for i in pubkey]
|
|
36
|
+
version = 0
|
|
37
|
+
return encode_address(os.getenv('KAS_NETWORK_PREFIX', 'kaspa'), pubkey, version)
|
|
38
|
+
|