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.
Files changed (129) hide show
  1. satkas/__init__.py +30 -0
  2. satkas/__main__.py +4 -0
  3. satkas/cli/__init__.py +1 -0
  4. satkas/cli/cli.py +41 -0
  5. satkas/core/__init__.py +9 -0
  6. satkas/core/blib/__init__.py +0 -0
  7. satkas/core/blib/baddress.py +12 -0
  8. satkas/core/blib/brpc.py +225 -0
  9. satkas/core/blib/btx_size.py +4 -0
  10. satkas/core/blib/scripting.py +92 -0
  11. satkas/core/db/__init__.py +0 -0
  12. satkas/core/db/models.py +185 -0
  13. satkas/core/klib/__init__.py +0 -0
  14. satkas/core/klib/kaddress.py +38 -0
  15. satkas/core/klib/kbech32.py +181 -0
  16. satkas/core/klib/kdatatype.py +109 -0
  17. satkas/core/klib/kgrpc.py +138 -0
  18. satkas/core/klib/kopcodes.py +207 -0
  19. satkas/core/klib/ksign.py +293 -0
  20. satkas/core/klib/ktransactions.py +143 -0
  21. satkas/core/klib/ktx_mass.py +164 -0
  22. satkas/core/klib/messages_pb2.py +41 -0
  23. satkas/core/klib/messages_pb2_grpc.py +97 -0
  24. satkas/core/klib/rpc_pb2.py +366 -0
  25. satkas/core/klib/rpc_pb2_grpc.py +24 -0
  26. satkas/core/klib/schnorr_signature.py +243 -0
  27. satkas/core/klib/script_builder.py +90 -0
  28. satkas/core/klib/scripting.py +323 -0
  29. satkas/core/klib/serialization.py +194 -0
  30. satkas/core/p2p/__init__.py +0 -0
  31. satkas/core/p2p/maker_p2p_node.py +212 -0
  32. satkas/core/p2p/p2p_node.py +339 -0
  33. satkas/core/p2p/taker_p2p_node.py +88 -0
  34. satkas/core/services/__init__.py +4 -0
  35. satkas/core/services/base_service.py +212 -0
  36. satkas/core/services/bitcoind_service.py +813 -0
  37. satkas/core/services/bitcoind_wallet_service.py +356 -0
  38. satkas/core/services/external_btc_wallet_service.py +83 -0
  39. satkas/core/services/external_kaspawallet_service.py +103 -0
  40. satkas/core/services/external_ln_wallet_service.py +178 -0
  41. satkas/core/services/internal_kaspawallet_service.py +216 -0
  42. satkas/core/services/kaspa_explorer_service.py +583 -0
  43. satkas/core/services/kaspad_service.py +620 -0
  44. satkas/core/services/kaspawallet_service.py +309 -0
  45. satkas/core/services/lnbits_service.py +329 -0
  46. satkas/core/services/lncli_service.py +438 -0
  47. satkas/core/services/mempool_service.py +581 -0
  48. satkas/core/services/rustykaspawallet_service.py +451 -0
  49. satkas/core/services/service_manager.py +602 -0
  50. satkas/core/services/tor_service.py +126 -0
  51. satkas/core/swapper/__init__.py +0 -0
  52. satkas/core/swapper/atomic_swap.py +370 -0
  53. satkas/core/swapper/counterparty.py +1618 -0
  54. satkas/core/swapper/maker.py +1403 -0
  55. satkas/core/swapper/swap_errors.py +71 -0
  56. satkas/core/swapper/swap_events.py +68 -0
  57. satkas/core/swapper/swap_interaction.py +121 -0
  58. satkas/core/swapper/taker.py +2163 -0
  59. satkas/main.py +293 -0
  60. satkas/main_app.kv +110 -0
  61. satkas/ui/__init__.py +0 -0
  62. satkas/ui/media/__init__.py +10 -0
  63. satkas/ui/media/bitcoin_logo.png +0 -0
  64. satkas/ui/media/kaspa-icon-green.png +0 -0
  65. satkas/ui/screens/__init__.py +0 -0
  66. satkas/ui/screens/base_responsive_layout/__init__.py +19 -0
  67. satkas/ui/screens/base_responsive_layout/base_responsive_layout.py +57 -0
  68. satkas/ui/screens/base_responsive_layout/common/__init__.py +7 -0
  69. satkas/ui/screens/base_responsive_layout/desktop/__init__.py +9 -0
  70. satkas/ui/screens/base_responsive_layout/desktop/desktop_layout.kv +21 -0
  71. satkas/ui/screens/base_responsive_layout/desktop/desktop_layout.py +81 -0
  72. satkas/ui/screens/base_responsive_layout/mobile/__init__.py +9 -0
  73. satkas/ui/screens/base_responsive_layout/mobile/mobile_layout.kv +18 -0
  74. satkas/ui/screens/base_responsive_layout/mobile/mobile_layout.py +36 -0
  75. satkas/ui/screens/base_responsive_layout/tablet/__init__.py +9 -0
  76. satkas/ui/screens/base_responsive_layout/tablet/tablet_layout.kv +22 -0
  77. satkas/ui/screens/base_responsive_layout/tablet/tablet_layout.py +37 -0
  78. satkas/ui/screens/dashboard_screen/__init__.py +4 -0
  79. satkas/ui/screens/dashboard_screen/dashboard_screen.kv +144 -0
  80. satkas/ui/screens/dashboard_screen/dashboard_screen.py +274 -0
  81. satkas/ui/screens/quick_swap_v2/__init__.py +8 -0
  82. satkas/ui/screens/quick_swap_v2/components/__init__.py +21 -0
  83. satkas/ui/screens/quick_swap_v2/components/attention_pulse.py +36 -0
  84. satkas/ui/screens/quick_swap_v2/components/contract_field.kv +86 -0
  85. satkas/ui/screens/quick_swap_v2/components/contract_field.py +68 -0
  86. satkas/ui/screens/quick_swap_v2/components/invoice_field.kv +103 -0
  87. satkas/ui/screens/quick_swap_v2/components/invoice_field.py +353 -0
  88. satkas/ui/screens/quick_swap_v2/components/payment_field.kv +73 -0
  89. satkas/ui/screens/quick_swap_v2/components/payment_field.py +112 -0
  90. satkas/ui/screens/quick_swap_v2/components/payout_bar.kv +137 -0
  91. satkas/ui/screens/quick_swap_v2/components/payout_bar.py +396 -0
  92. satkas/ui/screens/quick_swap_v2/components/peer_selector.kv +53 -0
  93. satkas/ui/screens/quick_swap_v2/components/peer_selector.py +367 -0
  94. satkas/ui/screens/quick_swap_v2/components/status_widget.kv +102 -0
  95. satkas/ui/screens/quick_swap_v2/components/status_widget.py +101 -0
  96. satkas/ui/screens/quick_swap_v2/components/swap_editor.kv +189 -0
  97. satkas/ui/screens/quick_swap_v2/components/swap_editor.py +471 -0
  98. satkas/ui/screens/quick_swap_v2/kivy_interaction.py +107 -0
  99. satkas/ui/screens/quick_swap_v2/quick_swap_v2_screen.kv +210 -0
  100. satkas/ui/screens/quick_swap_v2/quick_swap_v2_screen.py +1657 -0
  101. satkas/ui/screens/quick_swap_v2/swap_controller.py +423 -0
  102. satkas/ui/screens/setup_wizard/__init__.py +3 -0
  103. satkas/ui/screens/setup_wizard/pages/__init__.py +19 -0
  104. satkas/ui/screens/setup_wizard/pages/bitcoin_setup_page.kv +246 -0
  105. satkas/ui/screens/setup_wizard/pages/bitcoin_setup_page.py +152 -0
  106. satkas/ui/screens/setup_wizard/pages/btc_wallet_setup_page.kv +222 -0
  107. satkas/ui/screens/setup_wizard/pages/btc_wallet_setup_page.py +147 -0
  108. satkas/ui/screens/setup_wizard/pages/kaspa_wallet_setup_page.kv +315 -0
  109. satkas/ui/screens/setup_wizard/pages/kaspa_wallet_setup_page.py +227 -0
  110. satkas/ui/screens/setup_wizard/pages/kaspad_setup_page.kv +230 -0
  111. satkas/ui/screens/setup_wizard/pages/kaspad_setup_page.py +212 -0
  112. satkas/ui/screens/setup_wizard/pages/landing_page.kv +61 -0
  113. satkas/ui/screens/setup_wizard/pages/landing_page.py +26 -0
  114. satkas/ui/screens/setup_wizard/pages/ln_wallet_setup_page.kv +332 -0
  115. satkas/ui/screens/setup_wizard/pages/ln_wallet_setup_page.py +312 -0
  116. satkas/ui/screens/setup_wizard/pages/password_setup_page.kv +120 -0
  117. satkas/ui/screens/setup_wizard/pages/password_setup_page.py +70 -0
  118. satkas/ui/screens/setup_wizard/pages/tor_setup_page.kv +130 -0
  119. satkas/ui/screens/setup_wizard/pages/tor_setup_page.py +110 -0
  120. satkas/ui/screens/setup_wizard/setup_wizard.kv +39 -0
  121. satkas/ui/screens/setup_wizard/setup_wizard.py +227 -0
  122. satkas/ui/utils/__init__.py +5 -0
  123. satkas/ui/utils/qr_utils.py +51 -0
  124. satkas-0.1.0.dist-info/METADATA +111 -0
  125. satkas-0.1.0.dist-info/RECORD +129 -0
  126. satkas-0.1.0.dist-info/WHEEL +5 -0
  127. satkas-0.1.0.dist-info/entry_points.txt +2 -0
  128. satkas-0.1.0.dist-info/licenses/LICENSE +165 -0
  129. 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
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
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()
@@ -0,0 +1,9 @@
1
+
2
+ __all__ = [
3
+ 'blib',
4
+ 'db',
5
+ 'klib',
6
+ 'p2p',
7
+ 'services',
8
+ 'swapper'
9
+ ]
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()
@@ -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,4 @@
1
+ # 1 p2wpkh in, p2wsh contract + p2wpkh change. Sig 71–73 all ceil to 153.
2
+ BTC_FUNDING_VSIZE = 153
3
+ # 1 p2wsh HTLC in, 1 p2wpkh out (redeem; refund is 134). Height locktime, script 96.
4
+ BTC_SPEND_VSIZE = 143
@@ -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
@@ -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
+