rtm-sdk 1.0.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.
- raptoreum.py +232 -0
- rtm_sdk-1.0.0.dist-info/METADATA +63 -0
- rtm_sdk-1.0.0.dist-info/RECORD +5 -0
- rtm_sdk-1.0.0.dist-info/WHEEL +5 -0
- rtm_sdk-1.0.0.dist-info/top_level.txt +1 -0
raptoreum.py
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import urllib.request
|
|
3
|
+
import urllib.error
|
|
4
|
+
import base64
|
|
5
|
+
|
|
6
|
+
class RaptoreumRPCException(Exception):
|
|
7
|
+
def __init__(self, code, message):
|
|
8
|
+
self.code = code
|
|
9
|
+
self.message = message
|
|
10
|
+
super().__init__(f"RPC Error [{code}]: {message}")
|
|
11
|
+
|
|
12
|
+
class RaptoreumClient:
|
|
13
|
+
def __init__(self, host="127.0.0.1", port=8766, user="", password="", use_ssl=False):
|
|
14
|
+
self.host = host
|
|
15
|
+
self.port = port
|
|
16
|
+
self.user = user
|
|
17
|
+
self.password = password
|
|
18
|
+
self.use_ssl = use_ssl
|
|
19
|
+
scheme = "https" if use_ssl else "http"
|
|
20
|
+
self.url = f"{scheme}://{host}:{port}/"
|
|
21
|
+
|
|
22
|
+
def request(self, method, params=None):
|
|
23
|
+
if params is None:
|
|
24
|
+
params = []
|
|
25
|
+
|
|
26
|
+
payload = {
|
|
27
|
+
"jsonrpc": "1.0",
|
|
28
|
+
"id": "rtm-sdk-python",
|
|
29
|
+
"method": method,
|
|
30
|
+
"params": params
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
data = json.dumps(payload).encode("utf-8")
|
|
34
|
+
req = urllib.request.Request(self.url, data=data, headers={"Content-Type": "application/json"})
|
|
35
|
+
|
|
36
|
+
if self.user or self.password:
|
|
37
|
+
auth_str = f"{self.user}:{self.password}".encode("utf-8")
|
|
38
|
+
auth_header = b"Basic " + base64.b64encode(auth_str)
|
|
39
|
+
req.add_header("Authorization", auth_header.decode("utf-8"))
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
with urllib.request.urlopen(req, timeout=30) as response:
|
|
43
|
+
resp_data = json.loads(response.read().decode("utf-8"))
|
|
44
|
+
if resp_data.get("error"):
|
|
45
|
+
err = resp_data["error"]
|
|
46
|
+
raise RaptoreumRPCException(err.get("code"), err.get("message"))
|
|
47
|
+
return resp_data.get("result")
|
|
48
|
+
except urllib.error.HTTPError as e:
|
|
49
|
+
# RPC node often returns 500 on execution error with details in JSON
|
|
50
|
+
try:
|
|
51
|
+
resp_data = json.loads(e.read().decode("utf-8"))
|
|
52
|
+
if resp_data.get("error"):
|
|
53
|
+
err = resp_data["error"]
|
|
54
|
+
raise RaptoreumRPCException(err.get("code"), err.get("message"))
|
|
55
|
+
except Exception:
|
|
56
|
+
pass
|
|
57
|
+
raise Exception(f"HTTP Error {e.code}: {e.reason}")
|
|
58
|
+
except urllib.error.URLError as e:
|
|
59
|
+
raise Exception(f"Network Error: {e.reason}")
|
|
60
|
+
|
|
61
|
+
# Blockchain API
|
|
62
|
+
def getblockchaininfo(self):
|
|
63
|
+
return self.request("getblockchaininfo")
|
|
64
|
+
|
|
65
|
+
def getblockcount(self):
|
|
66
|
+
return self.request("getblockcount")
|
|
67
|
+
|
|
68
|
+
def getblockhash(self, height):
|
|
69
|
+
return self.request("getblockhash", [height])
|
|
70
|
+
|
|
71
|
+
def getblock(self, blockhash, verbosity=1):
|
|
72
|
+
return self.request("getblock", [blockhash, verbosity])
|
|
73
|
+
|
|
74
|
+
def getbestblockhash(self):
|
|
75
|
+
return self.request("getbestblockhash")
|
|
76
|
+
|
|
77
|
+
# Wallet API
|
|
78
|
+
def getbalance(self):
|
|
79
|
+
return self.request("getbalance")
|
|
80
|
+
|
|
81
|
+
def getnewaddress(self, label="", address_type="legacy"):
|
|
82
|
+
return self.request("getnewaddress", [label, address_type])
|
|
83
|
+
|
|
84
|
+
def sendtoaddress(self, address, amount, comment="", comment_to="", subtract_fee=False):
|
|
85
|
+
return self.request("sendtoaddress", [address, amount, comment, comment_to, subtract_fee])
|
|
86
|
+
|
|
87
|
+
def validateaddress(self, address):
|
|
88
|
+
return self.request("validateaddress", [address])
|
|
89
|
+
|
|
90
|
+
def sendmany(self, amounts, minconf=1, comment="", subtract_fee_from=None):
|
|
91
|
+
if subtract_fee_from is None:
|
|
92
|
+
subtract_fee_from = []
|
|
93
|
+
return self.request("sendmany", ["", amounts, minconf, comment, subtract_fee_from])
|
|
94
|
+
|
|
95
|
+
# Asset API
|
|
96
|
+
def listassets(self, mine=False):
|
|
97
|
+
return self.request("listassets", [mine])
|
|
98
|
+
|
|
99
|
+
def createasset(self, name, amount, options=None):
|
|
100
|
+
if options is None:
|
|
101
|
+
options = {}
|
|
102
|
+
return self.request("createasset", [name, amount, options])
|
|
103
|
+
|
|
104
|
+
def sendasset(self, asset_id, amount, address):
|
|
105
|
+
return self.request("sendasset", [asset_id, amount, address])
|
|
106
|
+
|
|
107
|
+
# Smartnode API
|
|
108
|
+
def smartnodelist(self):
|
|
109
|
+
return self.request("smartnodelist")
|
|
110
|
+
|
|
111
|
+
def smartnode_status(self):
|
|
112
|
+
return self.request("smartnode", ["status"])
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class RaptoreumWallet:
|
|
116
|
+
P = 2**256 - 2**32 - 977
|
|
117
|
+
N = 115792089237316195423570985008687907852837564279074904382605163141518161494337
|
|
118
|
+
A = 0
|
|
119
|
+
B = 7
|
|
120
|
+
Gx = 55066263022277343669578718895168534318117789391449992603003453700757405786800
|
|
121
|
+
Gy = 32670510020758816978083085130507043184471273380659244275771129596662263420883
|
|
122
|
+
G = (Gx, Gy)
|
|
123
|
+
|
|
124
|
+
@classmethod
|
|
125
|
+
def _inv(cls, a, n):
|
|
126
|
+
if a == 0: return 0
|
|
127
|
+
lm, hm = 1, 0
|
|
128
|
+
low, high = a % n, n
|
|
129
|
+
while low > 1:
|
|
130
|
+
r = high // low
|
|
131
|
+
nm, new = hm - lm * r, high - low * r
|
|
132
|
+
lm, low, hm, high = nm, new, lm, low
|
|
133
|
+
return lm % n
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
def _ec_add(cls, p, q):
|
|
137
|
+
if p is None: return q
|
|
138
|
+
if q is None: return p
|
|
139
|
+
(px, py), (qx, qy) = p, q
|
|
140
|
+
if px == qx and py == qy:
|
|
141
|
+
m = (3 * px * px + cls.A) * cls._inv(2 * py, cls.P)
|
|
142
|
+
else:
|
|
143
|
+
m = (qy - py) * cls._inv(qx - px, cls.P)
|
|
144
|
+
rx = (m * m - px - qx) % cls.P
|
|
145
|
+
ry = (m * (px - rx) - py) % cls.P
|
|
146
|
+
return (rx, ry)
|
|
147
|
+
|
|
148
|
+
@classmethod
|
|
149
|
+
def _ec_mul(cls, p, k):
|
|
150
|
+
r = None
|
|
151
|
+
b = p
|
|
152
|
+
while k > 0:
|
|
153
|
+
if k & 1: r = cls._ec_add(r, b)
|
|
154
|
+
b = cls._ec_add(b, b)
|
|
155
|
+
k >>= 1
|
|
156
|
+
return r
|
|
157
|
+
|
|
158
|
+
@classmethod
|
|
159
|
+
def generate_private_key(cls):
|
|
160
|
+
while True:
|
|
161
|
+
k_bytes = os.urandom(32)
|
|
162
|
+
k = int.from_bytes(k_bytes, 'big')
|
|
163
|
+
if 0 < k < cls.N:
|
|
164
|
+
return k_bytes
|
|
165
|
+
|
|
166
|
+
@classmethod
|
|
167
|
+
def private_key_to_address(cls, private_key_bytes):
|
|
168
|
+
k = int.from_bytes(private_key_bytes, 'big')
|
|
169
|
+
pub_point = cls._ec_mul(cls.G, k)
|
|
170
|
+
prefix = b'\x02' if pub_point[1] % 2 == 0 else b'\x03'
|
|
171
|
+
pub_bytes = prefix + pub_point[0].to_bytes(32, 'big')
|
|
172
|
+
|
|
173
|
+
# Hash160
|
|
174
|
+
sha = hashlib.sha256(pub_bytes).digest()
|
|
175
|
+
h = hashlib.new('ripemd160')
|
|
176
|
+
h.update(sha)
|
|
177
|
+
h160 = h.digest()
|
|
178
|
+
|
|
179
|
+
# Raptoreum version byte is 0x3c (60)
|
|
180
|
+
payload = b'\x3c' + h160
|
|
181
|
+
checksum = hashlib.sha256(hashlib.sha256(payload).digest()).digest()[:4]
|
|
182
|
+
|
|
183
|
+
# Base58Check
|
|
184
|
+
B58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
|
185
|
+
n = int.from_bytes(payload + checksum, 'big')
|
|
186
|
+
res = []
|
|
187
|
+
while n > 0:
|
|
188
|
+
n, r = divmod(n, 58)
|
|
189
|
+
res.append(B58[r])
|
|
190
|
+
res = ''.join(reversed(res))
|
|
191
|
+
pad = 0
|
|
192
|
+
for x in (payload + checksum):
|
|
193
|
+
if x == 0: pad += 1
|
|
194
|
+
else: break
|
|
195
|
+
return '1' * pad + res
|
|
196
|
+
|
|
197
|
+
@classmethod
|
|
198
|
+
def sign_message(cls, private_key_bytes, message_bytes):
|
|
199
|
+
z = int.from_bytes(hashlib.sha256(hashlib.sha256(message_bytes).digest()).digest(), 'big')
|
|
200
|
+
k_priv = int.from_bytes(private_key_bytes, 'big')
|
|
201
|
+
|
|
202
|
+
import random
|
|
203
|
+
while True:
|
|
204
|
+
k = random.randint(1, cls.N - 1)
|
|
205
|
+
r_point = cls._ec_mul(cls.G, k)
|
|
206
|
+
r = r_point[0] % cls.N
|
|
207
|
+
if r == 0: continue
|
|
208
|
+
s = (cls._inv(k, cls.N) * (z + r * k_priv)) % cls.N
|
|
209
|
+
if s == 0: continue
|
|
210
|
+
if s > cls.N // 2:
|
|
211
|
+
s = cls.N - s
|
|
212
|
+
|
|
213
|
+
# Format signature as DER-encoded
|
|
214
|
+
r_bytes = r.to_bytes(32, 'big').lstrip(b'\x00')
|
|
215
|
+
if len(r_bytes) == 0: r_bytes = b'\x00'
|
|
216
|
+
elif r_bytes[0] >= 0x80: r_bytes = b'\x00' + r_bytes
|
|
217
|
+
|
|
218
|
+
s_bytes = s.to_bytes(32, 'big').lstrip(b'\x00')
|
|
219
|
+
if len(s_bytes) == 0: s_bytes = b'\x00'
|
|
220
|
+
elif s_bytes[0] >= 0x80: s_bytes = b'\x00' + s_bytes
|
|
221
|
+
|
|
222
|
+
der = bytearray()
|
|
223
|
+
der.append(0x30)
|
|
224
|
+
der.append(len(r_bytes) + len(s_bytes) + 4)
|
|
225
|
+
der.append(0x02)
|
|
226
|
+
der.append(len(r_bytes))
|
|
227
|
+
der.extend(r_bytes)
|
|
228
|
+
der.append(0x02)
|
|
229
|
+
der.append(len(s_bytes))
|
|
230
|
+
der.extend(s_bytes)
|
|
231
|
+
return bytes(der)
|
|
232
|
+
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rtm-sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Official Python SDK for Raptoreum (RTM) JSON-RPC API
|
|
5
|
+
Home-page: https://github.com/Raptor3um/rtm-sdk
|
|
6
|
+
Author: Raptoreum Developers
|
|
7
|
+
Author-email: dev@raptoreum.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.6
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Dynamic: author
|
|
14
|
+
Dynamic: author-email
|
|
15
|
+
Dynamic: classifier
|
|
16
|
+
Dynamic: description
|
|
17
|
+
Dynamic: description-content-type
|
|
18
|
+
Dynamic: home-page
|
|
19
|
+
Dynamic: requires-python
|
|
20
|
+
Dynamic: summary
|
|
21
|
+
|
|
22
|
+
# Raptoreum (RTM) SDK - Python
|
|
23
|
+
|
|
24
|
+
A lightweight, zero-dependency Python client wrapper for the Raptoreum Core JSON-RPC interface.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
You can install this SDK from PyPI:
|
|
31
|
+
```bash
|
|
32
|
+
pip install rtm-sdk
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Or install it locally from source:
|
|
36
|
+
```bash
|
|
37
|
+
cd python
|
|
38
|
+
pip install -e .
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from raptoreum import RaptoreumClient
|
|
47
|
+
|
|
48
|
+
# Initialize the client
|
|
49
|
+
client = RaptoreumClient(
|
|
50
|
+
host="127.0.0.1",
|
|
51
|
+
port=8766,
|
|
52
|
+
user="your_rpc_user",
|
|
53
|
+
password="your_rpc_password"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Fetch blockchain info
|
|
57
|
+
info = client.getblockchaininfo()
|
|
58
|
+
print(f"Current Block Height: {info['blocks']}")
|
|
59
|
+
|
|
60
|
+
# Get wallet balance
|
|
61
|
+
balance = client.getbalance()
|
|
62
|
+
print(f"Hot Wallet Balance: {balance} RTM")
|
|
63
|
+
```
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
raptoreum.py,sha256=gOOk0Aoho1MKuWZmogPkqYN6fCx7Ed7CpzgMCjwgcbM,8016
|
|
2
|
+
rtm_sdk-1.0.0.dist-info/METADATA,sha256=DV7vprvpMuySapkk1s7IUDvSNzJDUpbh3QNfsFH1Kko,1327
|
|
3
|
+
rtm_sdk-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
4
|
+
rtm_sdk-1.0.0.dist-info/top_level.txt,sha256=2zGwhNLyErxyHOzz1xw7BhKEOtEZxjvdadGeVbxnnK4,10
|
|
5
|
+
rtm_sdk-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
raptoreum
|