hive-nectar 0.0.2__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.

Potentially problematic release.


This version of hive-nectar might be problematic. Click here for more details.

Files changed (86) hide show
  1. hive_nectar-0.0.2.dist-info/METADATA +182 -0
  2. hive_nectar-0.0.2.dist-info/RECORD +86 -0
  3. hive_nectar-0.0.2.dist-info/WHEEL +4 -0
  4. hive_nectar-0.0.2.dist-info/entry_points.txt +2 -0
  5. hive_nectar-0.0.2.dist-info/licenses/LICENSE.txt +23 -0
  6. nectar/__init__.py +32 -0
  7. nectar/account.py +4371 -0
  8. nectar/amount.py +475 -0
  9. nectar/asciichart.py +270 -0
  10. nectar/asset.py +82 -0
  11. nectar/block.py +446 -0
  12. nectar/blockchain.py +1178 -0
  13. nectar/blockchaininstance.py +2284 -0
  14. nectar/blockchainobject.py +221 -0
  15. nectar/blurt.py +563 -0
  16. nectar/cli.py +6285 -0
  17. nectar/comment.py +1217 -0
  18. nectar/community.py +513 -0
  19. nectar/constants.py +111 -0
  20. nectar/conveyor.py +309 -0
  21. nectar/discussions.py +1709 -0
  22. nectar/exceptions.py +149 -0
  23. nectar/hive.py +546 -0
  24. nectar/hivesigner.py +420 -0
  25. nectar/imageuploader.py +72 -0
  26. nectar/instance.py +129 -0
  27. nectar/market.py +1013 -0
  28. nectar/memo.py +449 -0
  29. nectar/message.py +357 -0
  30. nectar/nodelist.py +444 -0
  31. nectar/price.py +557 -0
  32. nectar/profile.py +65 -0
  33. nectar/rc.py +308 -0
  34. nectar/snapshot.py +726 -0
  35. nectar/steem.py +582 -0
  36. nectar/storage.py +53 -0
  37. nectar/transactionbuilder.py +622 -0
  38. nectar/utils.py +545 -0
  39. nectar/version.py +2 -0
  40. nectar/vote.py +557 -0
  41. nectar/wallet.py +472 -0
  42. nectar/witness.py +617 -0
  43. nectarapi/__init__.py +11 -0
  44. nectarapi/exceptions.py +123 -0
  45. nectarapi/graphenerpc.py +589 -0
  46. nectarapi/node.py +178 -0
  47. nectarapi/noderpc.py +229 -0
  48. nectarapi/rpcutils.py +97 -0
  49. nectarapi/version.py +2 -0
  50. nectarbase/__init__.py +14 -0
  51. nectarbase/ledgertransactions.py +75 -0
  52. nectarbase/memo.py +243 -0
  53. nectarbase/objects.py +429 -0
  54. nectarbase/objecttypes.py +22 -0
  55. nectarbase/operationids.py +102 -0
  56. nectarbase/operations.py +1297 -0
  57. nectarbase/signedtransactions.py +48 -0
  58. nectarbase/transactions.py +11 -0
  59. nectarbase/version.py +2 -0
  60. nectargrapheneapi/__init__.py +6 -0
  61. nectargraphenebase/__init__.py +27 -0
  62. nectargraphenebase/account.py +846 -0
  63. nectargraphenebase/aes.py +52 -0
  64. nectargraphenebase/base58.py +192 -0
  65. nectargraphenebase/bip32.py +494 -0
  66. nectargraphenebase/bip38.py +134 -0
  67. nectargraphenebase/chains.py +149 -0
  68. nectargraphenebase/dictionary.py +3 -0
  69. nectargraphenebase/ecdsasig.py +326 -0
  70. nectargraphenebase/objects.py +123 -0
  71. nectargraphenebase/objecttypes.py +6 -0
  72. nectargraphenebase/operationids.py +3 -0
  73. nectargraphenebase/operations.py +23 -0
  74. nectargraphenebase/prefix.py +11 -0
  75. nectargraphenebase/py23.py +38 -0
  76. nectargraphenebase/signedtransactions.py +201 -0
  77. nectargraphenebase/types.py +419 -0
  78. nectargraphenebase/unsignedtransactions.py +283 -0
  79. nectargraphenebase/version.py +2 -0
  80. nectarstorage/__init__.py +38 -0
  81. nectarstorage/base.py +306 -0
  82. nectarstorage/exceptions.py +16 -0
  83. nectarstorage/interfaces.py +237 -0
  84. nectarstorage/masterpassword.py +239 -0
  85. nectarstorage/ram.py +30 -0
  86. nectarstorage/sqlite.py +334 -0
nectarapi/node.py ADDED
@@ -0,0 +1,178 @@
1
+ # -*- coding: utf-8 -*-
2
+ import logging
3
+ import re
4
+ import time
5
+
6
+ from .exceptions import CallRetriesReached, NumRetriesReached
7
+
8
+ log = logging.getLogger(__name__)
9
+
10
+
11
+ class Node(object):
12
+ def __init__(self, url):
13
+ self.url = url
14
+ self.error_cnt = 0
15
+ self.error_cnt_call = 0
16
+
17
+ def __repr__(self):
18
+ return self.url
19
+
20
+
21
+ class Nodes(list):
22
+ """Stores Node URLs and error counts"""
23
+
24
+ def __init__(self, urls, num_retries, num_retries_call):
25
+ self.set_node_urls(urls)
26
+ self.num_retries = num_retries
27
+ self.num_retries_call = num_retries_call
28
+
29
+ def set_node_urls(self, urls):
30
+ if isinstance(urls, str):
31
+ url_list = re.split(r",|;", urls)
32
+ if url_list is None:
33
+ url_list = [urls]
34
+ elif isinstance(urls, Nodes):
35
+ url_list = [urls[i].url for i in range(len(urls))]
36
+ elif isinstance(urls, (list, tuple, set)):
37
+ url_list = urls
38
+ elif urls is not None:
39
+ url_list = [urls]
40
+ else:
41
+ url_list = []
42
+ super(Nodes, self).__init__([Node(x) for x in url_list])
43
+ self.current_node_index = -1
44
+ self.freeze_current_node = False
45
+
46
+ def __iter__(self):
47
+ return self
48
+
49
+ def __next__(self):
50
+ next_node_count = 0
51
+ if self.freeze_current_node:
52
+ return self.url
53
+ while next_node_count == 0 and (
54
+ self.num_retries < 0 or self.node.error_cnt < self.num_retries
55
+ ):
56
+ self.current_node_index += 1
57
+ if self.current_node_index >= self.working_nodes_count:
58
+ self.current_node_index = 0
59
+ next_node_count += 1
60
+ if next_node_count > self.working_nodes_count + 1:
61
+ raise StopIteration
62
+ return self.url
63
+
64
+ next = __next__ # Python 2
65
+
66
+ def export_working_nodes(self):
67
+ nodes_list = []
68
+ for i in range(len(self)):
69
+ if self.num_retries < 0 or self[i].error_cnt <= self.num_retries:
70
+ nodes_list.append(self[i].url)
71
+ return nodes_list
72
+
73
+ def __repr__(self):
74
+ nodes_list = self.export_working_nodes()
75
+ return str(nodes_list)
76
+
77
+ @property
78
+ def working_nodes_count(self):
79
+ n = 0
80
+ if self.freeze_current_node:
81
+ i = self.current_node_index
82
+ if self.current_node_index < 0:
83
+ i = 0
84
+ if self.num_retries < 0 or self[i].error_cnt <= self.num_retries:
85
+ n += 1
86
+ return n
87
+ for i in range(len(self)):
88
+ if self.num_retries < 0 or self[i].error_cnt <= self.num_retries:
89
+ n += 1
90
+ return n
91
+
92
+ @property
93
+ def url(self):
94
+ if self.node is None:
95
+ return ""
96
+ return self.node.url
97
+
98
+ @property
99
+ def node(self):
100
+ if self.current_node_index < 0:
101
+ return self[0]
102
+ return self[self.current_node_index]
103
+
104
+ @property
105
+ def error_cnt(self):
106
+ if self.node is None:
107
+ return 0
108
+ return self.node.error_cnt
109
+
110
+ @property
111
+ def error_cnt_call(self):
112
+ if self.node is None:
113
+ return 0
114
+ return self.node.error_cnt_call
115
+
116
+ @property
117
+ def num_retries_call_reached(self):
118
+ return self.error_cnt_call >= self.num_retries_call
119
+
120
+ def disable_node(self):
121
+ """Disable current node"""
122
+ if self.node is not None and self.num_retries_call >= 0:
123
+ self.node.error_cnt_call = self.num_retries_call
124
+
125
+ def increase_error_cnt(self):
126
+ """Increase node error count for current node"""
127
+ if self.node is not None:
128
+ self.node.error_cnt += 1
129
+
130
+ def increase_error_cnt_call(self):
131
+ """Increase call error count for current node"""
132
+ if self.node is not None:
133
+ self.node.error_cnt_call += 1
134
+
135
+ def reset_error_cnt_call(self):
136
+ """Set call error count for current node to zero"""
137
+ if self.node is not None:
138
+ self.node.error_cnt_call = 0
139
+
140
+ def reset_error_cnt(self):
141
+ """Set node error count for current node to zero"""
142
+ if self.node is not None:
143
+ self.node.error_cnt = 0
144
+
145
+ def sleep_and_check_retries(self, errorMsg=None, sleep=True, call_retry=False, showMsg=True):
146
+ """Sleep and check if num_retries is reached"""
147
+ if errorMsg:
148
+ log.warning("Error: {}".format(errorMsg))
149
+ if call_retry:
150
+ cnt = self.error_cnt_call
151
+ if self.num_retries_call >= 0 and self.error_cnt_call > self.num_retries_call:
152
+ raise CallRetriesReached()
153
+ else:
154
+ cnt = self.error_cnt
155
+ if self.num_retries >= 0 and self.error_cnt > self.num_retries:
156
+ raise NumRetriesReached()
157
+
158
+ if showMsg:
159
+ if call_retry:
160
+ log.warning(
161
+ "Retry RPC Call on node: %s (%d/%d) \n" % (self.url, cnt, self.num_retries_call)
162
+ )
163
+ else:
164
+ log.warning(
165
+ "Lost connection or internal error on node: %s (%d/%d) \n"
166
+ % (self.url, cnt, self.num_retries)
167
+ )
168
+ if not sleep:
169
+ return
170
+ if cnt < 1:
171
+ sleeptime = 0
172
+ elif cnt < 10:
173
+ sleeptime = (cnt - 1) * 1.5 + 0.5
174
+ else:
175
+ sleeptime = 10
176
+ if sleeptime:
177
+ log.warning("Retrying in %d seconds\n" % sleeptime)
178
+ time.sleep(sleeptime)
nectarapi/noderpc.py ADDED
@@ -0,0 +1,229 @@
1
+ # -*- coding: utf-8 -*-
2
+ import logging
3
+ import re
4
+
5
+ from . import exceptions
6
+ from .graphenerpc import GrapheneRPC
7
+
8
+ log = logging.getLogger(__name__)
9
+
10
+
11
+ class NodeRPC(GrapheneRPC):
12
+ """This class allows to call API methods exposed by the witness node via
13
+ websockets / rpc-json.
14
+
15
+ :param str urls: Either a single Websocket/Http URL, or a list of URLs
16
+ :param str user: Username for Authentication
17
+ :param str password: Password for Authentication
18
+ :param int num_retries: Try x times to num_retries to a node on disconnect, -1 for indefinitely
19
+ :param int num_retries_call: Repeat num_retries_call times a rpc call on node error (default is 5)
20
+ :param int timeout: Timeout setting for https nodes (default is 60)
21
+ :param bool use_condenser: Use the old condenser_api rpc protocol on nodes with version
22
+ 0.19.4 or higher. The settings has no effect on nodes with version of 0.19.3 or lower.
23
+ :param bool use_tor: When set to true, 'socks5h://localhost:9050' is set as proxy
24
+
25
+ """
26
+
27
+ def __init__(self, *args, **kwargs):
28
+ """Init NodeRPC
29
+
30
+ :param str urls: Either a single Websocket/Http URL, or a list of URLs
31
+ :param str user: Username for Authentication
32
+ :param str password: Password for Authentication
33
+ :param int num_retries: Try x times to num_retries to a node on disconnect, -1 for indefinitely
34
+ :param int num_retries_call: Repeat num_retries_call times a rpc call on node error (default is 5)
35
+ :param int timeout: Timeout setting for https nodes (default is 60)
36
+ :param bool use_tor: When set to true, 'socks5h://localhost:9050' is set as proxy
37
+
38
+ """
39
+ super(NodeRPC, self).__init__(*args, **kwargs)
40
+ self.next_node_on_empty_reply = False
41
+
42
+ def set_next_node_on_empty_reply(self, next_node_on_empty_reply=True):
43
+ """Switch to next node on empty reply for the next rpc call"""
44
+ self.next_node_on_empty_reply = next_node_on_empty_reply
45
+
46
+ def rpcexec(self, payload):
47
+ """Execute a call by sending the payload.
48
+ It makes use of the GrapheneRPC library.
49
+ In here, we mostly deal with Steem specific error handling
50
+
51
+ :param json payload: Payload data
52
+ :raises ValueError: if the server does not respond in proper JSON format
53
+ :raises RPCError: if the server returns an error
54
+ """
55
+ if self.url is None:
56
+ raise exceptions.RPCConnection("RPC is not connected!")
57
+ doRetry = True
58
+ maxRetryCountReached = False
59
+ while doRetry and not maxRetryCountReached:
60
+ doRetry = False
61
+ try:
62
+ # Forward call to GrapheneWebsocketRPC and catch+evaluate errors
63
+ reply = super(NodeRPC, self).rpcexec(payload)
64
+ if (
65
+ self.next_node_on_empty_reply
66
+ and not bool(reply)
67
+ and self.nodes.working_nodes_count > 1
68
+ ):
69
+ self._retry_on_next_node("Empty Reply")
70
+ doRetry = True
71
+ self.next_node_on_empty_reply = True
72
+ else:
73
+ self.next_node_on_empty_reply = False
74
+ return reply
75
+ except exceptions.RPCErrorDoRetry as e:
76
+ msg = exceptions.decodeRPCErrorMsg(e).strip()
77
+ try:
78
+ self.nodes.sleep_and_check_retries(str(msg), call_retry=True)
79
+ doRetry = True
80
+ except exceptions.CallRetriesReached:
81
+ if self.nodes.working_nodes_count > 1:
82
+ self._retry_on_next_node(msg)
83
+ doRetry = True
84
+ else:
85
+ self.next_node_on_empty_reply = False
86
+ raise exceptions.CallRetriesReached
87
+ except exceptions.RPCError as e:
88
+ try:
89
+ doRetry = self._check_error_message(e, self.error_cnt_call)
90
+ except exceptions.CallRetriesReached:
91
+ msg = exceptions.decodeRPCErrorMsg(e).strip()
92
+ if self.nodes.working_nodes_count > 1:
93
+ self._retry_on_next_node(msg)
94
+ doRetry = True
95
+ else:
96
+ self.next_node_on_empty_reply = False
97
+ raise exceptions.CallRetriesReached
98
+ except Exception as e:
99
+ self.next_node_on_empty_reply = False
100
+ raise e
101
+ maxRetryCountReached = self.nodes.num_retries_call_reached
102
+ self.next_node_on_empty_reply = False
103
+
104
+ def _retry_on_next_node(self, error_msg):
105
+ self.nodes.increase_error_cnt()
106
+ self.nodes.sleep_and_check_retries(error_msg, sleep=False, call_retry=False)
107
+ self.next()
108
+
109
+ def _check_error_message(self, e, cnt):
110
+ """Check error message and decide what to do"""
111
+ doRetry = False
112
+ msg = exceptions.decodeRPCErrorMsg(e).strip()
113
+ if re.search("missing required active authority", msg):
114
+ raise exceptions.MissingRequiredActiveAuthority
115
+ elif re.match("^no method with name.*", msg):
116
+ raise exceptions.NoMethodWithName(msg)
117
+ elif re.search("Could not find method", msg):
118
+ raise exceptions.NoMethodWithName(msg)
119
+ elif re.search("Unknown Transaction", msg):
120
+ raise exceptions.UnknownTransaction(msg)
121
+ elif re.search("Could not find API", msg):
122
+ if self._check_api_name(msg):
123
+ if self.nodes.working_nodes_count > 1 and self.nodes.num_retries > -1:
124
+ self.nodes.disable_node()
125
+ self._switch_to_next_node(msg, "ApiNotSupported")
126
+ doRetry = True
127
+ else:
128
+ raise exceptions.ApiNotSupported(msg)
129
+ else:
130
+ raise exceptions.NoApiWithName(msg)
131
+ elif re.search("follow_api_plugin not enabled", msg):
132
+ if self.nodes.working_nodes_count > 1 and self.nodes.num_retries > -1:
133
+ self._switch_to_next_node(str(e))
134
+ doRetry = True
135
+ else:
136
+ raise exceptions.FollowApiNotEnabled(msg)
137
+ elif re.search("irrelevant signature included", msg):
138
+ raise exceptions.UnnecessarySignatureDetected(msg)
139
+ elif re.search("WinError", msg):
140
+ raise exceptions.RPCError(msg)
141
+ elif re.search("Invalid parameters", msg):
142
+ raise exceptions.InvalidParameters()
143
+ elif re.search("Supported by Hivemind", msg):
144
+ raise exceptions.SupportedByHivemind()
145
+ elif re.search("Could not find filtered operation", msg):
146
+ raise exceptions.FilteredItemNotFound(msg)
147
+ elif re.search("Unable to acquire database lock", msg):
148
+ self.nodes.sleep_and_check_retries(str(msg), call_retry=True)
149
+ doRetry = True
150
+ elif re.search("Request Timeout", msg):
151
+ self.nodes.sleep_and_check_retries(str(msg), call_retry=True)
152
+ doRetry = True
153
+ elif re.search("Bad or missing upstream response", msg):
154
+ self.nodes.sleep_and_check_retries(str(msg), call_retry=True)
155
+ doRetry = True
156
+ elif re.search("Internal Error", msg) or re.search("Unknown exception", msg):
157
+ self.nodes.sleep_and_check_retries(str(msg), call_retry=True)
158
+ doRetry = True
159
+ elif re.search("!check_max_block_age", str(e)):
160
+ self._switch_to_next_node(str(e))
161
+ doRetry = True
162
+ elif re.search("Server error", str(e)):
163
+ self._switch_to_next_node(str(e))
164
+ doRetry = True
165
+ elif re.search("Can only vote once every 3 seconds", msg):
166
+ raise exceptions.VotedBeforeWaitTimeReached(msg)
167
+ elif re.search("out_of_rangeEEEE: unknown key", msg) or re.search(
168
+ "unknown key:unknown key", msg
169
+ ):
170
+ raise exceptions.UnkownKey(msg)
171
+ elif re.search("Assert Exception:v.is_object(): Input data have to treated as object", msg):
172
+ raise exceptions.UnhandledRPCError(
173
+ "Use Operation(op, appbase=True) to prevent error: " + msg
174
+ )
175
+ elif re.search("Client returned invalid format. Expected JSON!", msg):
176
+ if self.nodes.working_nodes_count > 1 and self.nodes.num_retries > -1:
177
+ self.nodes.disable_node()
178
+ self._switch_to_next_node(msg)
179
+ doRetry = True
180
+ else:
181
+ raise exceptions.UnhandledRPCError(msg)
182
+ elif msg:
183
+ raise exceptions.UnhandledRPCError(msg)
184
+ else:
185
+ raise e
186
+ return doRetry
187
+
188
+ def _switch_to_next_node(self, msg, error_type="UnhandledRPCError"):
189
+ if self.nodes.working_nodes_count == 1:
190
+ if error_type == "UnhandledRPCError":
191
+ raise exceptions.UnhandledRPCError(msg)
192
+ elif error_type == "ApiNotSupported":
193
+ raise exceptions.ApiNotSupported(msg)
194
+ self.nodes.increase_error_cnt()
195
+ self.nodes.sleep_and_check_retries(str(msg), sleep=False)
196
+ self.next()
197
+
198
+ def _check_api_name(self, msg):
199
+ error_start = "Could not find API"
200
+ known_apis = [
201
+ "account_history_api",
202
+ "tags_api",
203
+ "database_api",
204
+ "market_history_api",
205
+ "block_api",
206
+ "account_by_key_api",
207
+ "chain_api",
208
+ "follow_api",
209
+ "condenser_api",
210
+ "debug_node_api",
211
+ "witness_api",
212
+ "test_api",
213
+ "bridge",
214
+ "network_broadcast_api",
215
+ ]
216
+ for api in known_apis:
217
+ if re.search(error_start + " " + api, msg):
218
+ return True
219
+ if msg[-18:] == error_start:
220
+ return True
221
+ return False
222
+
223
+ def get_account(self, name, **kwargs):
224
+ """Get full account details from account name
225
+
226
+ :param str name: Account name
227
+ """
228
+ if isinstance(name, str):
229
+ return self.get_accounts([name], **kwargs)
nectarapi/rpcutils.py ADDED
@@ -0,0 +1,97 @@
1
+ # -*- coding: utf-8 -*-
2
+ import json
3
+ import logging
4
+
5
+ log = logging.getLogger(__name__)
6
+
7
+
8
+ def is_network_appbase_ready(props):
9
+ """Checks if the network is appbase ready"""
10
+ if "STEEMIT_BLOCKCHAIN_VERSION" in props:
11
+ return False
12
+ elif "STEEM_BLOCKCHAIN_VERSION" in props:
13
+ return True
14
+ elif "HIVE_BLOCKCHAIN_VERSION" in props:
15
+ return True
16
+ else:
17
+ return False
18
+
19
+
20
+ def get_query(appbase, request_id, api_name, name, args):
21
+ query = []
22
+ if not appbase or api_name == "condenser_api":
23
+ query = {
24
+ "method": "call",
25
+ "params": [api_name, name, list(args)],
26
+ "jsonrpc": "2.0",
27
+ "id": request_id,
28
+ }
29
+ else:
30
+ args = json.loads(json.dumps(args))
31
+ # print(args)
32
+ if len(args) > 0 and isinstance(args, list) and isinstance(args[0], dict):
33
+ query = {
34
+ "method": api_name + "." + name,
35
+ "params": args[0],
36
+ "jsonrpc": "2.0",
37
+ "id": request_id,
38
+ }
39
+ elif (
40
+ len(args) > 0
41
+ and isinstance(args, list)
42
+ and isinstance(args[0], list)
43
+ and len(args[0]) > 0
44
+ and isinstance(args[0][0], dict)
45
+ ):
46
+ for a in args[0]:
47
+ query.append(
48
+ {
49
+ "method": api_name + "." + name,
50
+ "params": a,
51
+ "jsonrpc": "2.0",
52
+ "id": request_id,
53
+ }
54
+ )
55
+ request_id += 1
56
+ elif args:
57
+ query = {
58
+ "method": "call",
59
+ "params": [api_name, name, list(args)],
60
+ "jsonrpc": "2.0",
61
+ "id": request_id,
62
+ }
63
+ request_id += 1
64
+ elif api_name == "condenser_api":
65
+ query = {
66
+ "method": api_name + "." + name,
67
+ "jsonrpc": "2.0",
68
+ "params": [],
69
+ "id": request_id,
70
+ }
71
+ else:
72
+ query = {
73
+ "method": api_name + "." + name,
74
+ "jsonrpc": "2.0",
75
+ "params": {},
76
+ "id": request_id,
77
+ }
78
+ return query
79
+
80
+
81
+ def get_api_name(appbase, *args, **kwargs):
82
+ if not appbase:
83
+ # Sepcify the api to talk to
84
+ if ("api" in kwargs) and len(kwargs["api"]) > 0:
85
+ api_name = kwargs["api"].replace("_api", "") + "_api"
86
+ else:
87
+ api_name = None
88
+ else:
89
+ # Sepcify the api to talk to
90
+ if ("api" in kwargs) and len(kwargs["api"]) > 0:
91
+ if kwargs["api"] not in ["jsonrpc", "hive", "bridge"]:
92
+ api_name = kwargs["api"].replace("_api", "") + "_api"
93
+ else:
94
+ api_name = kwargs["api"]
95
+ else:
96
+ api_name = "condenser_api"
97
+ return api_name
nectarapi/version.py ADDED
@@ -0,0 +1,2 @@
1
+ """THIS FILE IS GENERATED FROM nectar PYPROJECT.TOML."""
2
+ version = '0.00.02'
nectarbase/__init__.py ADDED
@@ -0,0 +1,14 @@
1
+ """nectarbase."""
2
+
3
+ from .version import version as __version__
4
+
5
+ __all__ = [
6
+ "memo",
7
+ "objects",
8
+ "objecttypes",
9
+ "operationids",
10
+ "operations",
11
+ "signedtransactions",
12
+ "ledgertransactions",
13
+ "transactions",
14
+ ]
@@ -0,0 +1,75 @@
1
+ # -*- coding: utf-8 -*-
2
+ import logging
3
+
4
+ from nectargraphenebase.account import PublicKey
5
+ from nectargraphenebase.chains import known_chains
6
+ from nectargraphenebase.py23 import py23_bytes
7
+ from nectargraphenebase.types import (
8
+ Array,
9
+ Signature,
10
+ )
11
+ from nectargraphenebase.unsignedtransactions import (
12
+ Unsigned_Transaction as GrapheneUnsigned_Transaction,
13
+ )
14
+
15
+ from .operations import Operation
16
+
17
+ log = logging.getLogger(__name__)
18
+
19
+
20
+ class Ledger_Transaction(GrapheneUnsigned_Transaction):
21
+ """Create an unsigned transaction and offer method to send it to a ledger device for signing
22
+
23
+ :param num ref_block_num:
24
+ :param num ref_block_prefix:
25
+ :param str expiration: expiration date
26
+ :param array operations: array of operations
27
+ :param dict custom_chains: custom chain which should be added to the known chains
28
+ """
29
+
30
+ def __init__(self, *args, **kwargs):
31
+ self.known_chains = known_chains
32
+ custom_chain = kwargs.get("custom_chains", {})
33
+ if len(custom_chain) > 0:
34
+ for c in custom_chain:
35
+ if c not in self.known_chains:
36
+ self.known_chains[c] = custom_chain[c]
37
+ super(Ledger_Transaction, self).__init__(*args, **kwargs)
38
+
39
+ def add_custom_chains(self, custom_chain):
40
+ if len(custom_chain) > 0:
41
+ for c in custom_chain:
42
+ if c not in self.known_chains:
43
+ self.known_chains[c] = custom_chain[c]
44
+
45
+ def getOperationKlass(self):
46
+ return Operation
47
+
48
+ def getKnownChains(self):
49
+ return self.known_chains
50
+
51
+ def sign(self, path="48'/13'/0'/0'/0'", chain="STEEM"):
52
+ from ledgerblue.comm import getDongle
53
+
54
+ dongle = getDongle(True)
55
+ apdu_list = self.build_apdu(path, chain)
56
+ for apdu in apdu_list:
57
+ result = dongle.exchange(py23_bytes(apdu))
58
+ dongle.close()
59
+ sigs = []
60
+ signature = result
61
+ sigs.append(Signature(signature))
62
+ self.data["signatures"] = Array(sigs)
63
+ return self
64
+
65
+ def get_pubkey(self, path="48'/13'/0'/0'/0'", request_screen_approval=False, prefix="STM"):
66
+ from ledgerblue.comm import getDongle
67
+
68
+ dongle = getDongle(True)
69
+ apdu = self.build_apdu_pubkey(path, request_screen_approval)
70
+ result = dongle.exchange(py23_bytes(apdu))
71
+ dongle.close()
72
+ offset = 1 + result[0]
73
+ address = result[offset + 1 : offset + 1 + result[offset]]
74
+ # public_key = result[1: 1 + result[0]]
75
+ return PublicKey(address.decode(), prefix=prefix)