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
nectar/account.py ADDED
@@ -0,0 +1,4371 @@
1
+ # -*- coding: utf-8 -*-
2
+ import json
3
+ import logging
4
+ import random
5
+ from datetime import date, datetime, time, timedelta, timezone
6
+
7
+ from prettytable import PrettyTable
8
+
9
+ from nectar.amount import Amount
10
+ from nectar.constants import (
11
+ STEEM_1_PERCENT,
12
+ STEEM_100_PERCENT,
13
+ STEEM_VOTE_REGENERATION_SECONDS,
14
+ STEEM_VOTING_MANA_REGENERATION_SECONDS,
15
+ )
16
+ from nectar.instance import shared_blockchain_instance
17
+ from nectar.rc import RC
18
+ from nectarapi.exceptions import (
19
+ ApiNotSupported,
20
+ FilteredItemNotFound,
21
+ MissingRequiredActiveAuthority,
22
+ SupportedByHivemind,
23
+ )
24
+ from nectarbase import operations
25
+ from nectargraphenebase.account import PasswordKey, PublicKey
26
+ from nectargraphenebase.py23 import integer_types, string_types
27
+
28
+ from .blockchain import Blockchain
29
+ from .blockchainobject import BlockchainObject
30
+ from .exceptions import AccountDoesNotExistsException, OfflineHasNoRPCException
31
+ from .utils import (
32
+ addTzInfo,
33
+ formatTimedelta,
34
+ formatTimeString,
35
+ remove_from_dict,
36
+ reputation_to_score,
37
+ )
38
+
39
+ log = logging.getLogger(__name__)
40
+
41
+
42
+ def extract_account_name(account):
43
+ if isinstance(account, str):
44
+ return account
45
+ elif isinstance(account, Account):
46
+ return account["name"]
47
+ elif isinstance(account, dict) and "name" in account:
48
+ return account["name"]
49
+ else:
50
+ return ""
51
+
52
+
53
+ class Account(BlockchainObject):
54
+ """This class allows to easily access Account data
55
+
56
+ :param str account: Name of the account
57
+ :param Steem/Hive blockchain_instance: Hive or Steem
58
+ instance
59
+ :param bool lazy: Use lazy loading
60
+ :param bool full: Obtain all account data including orders, positions,
61
+ etc.
62
+ :param Hive hive_instance: Hive instance
63
+ :param Steem steem_instance: Steem instance
64
+ :returns: Account data
65
+ :rtype: dictionary
66
+ :raises nectar.exceptions.AccountDoesNotExistsException: if account
67
+ does not exist
68
+
69
+ Instances of this class are dictionaries that come with additional
70
+ methods (see below) that allow dealing with an account and its
71
+ corresponding functions.
72
+
73
+ .. code-block:: python
74
+
75
+ >>> from nectar.account import Account
76
+ >>> from nectar import Hive
77
+ >>> from nectar.nodelist import NodeList
78
+ >>> nodelist = NodeList()
79
+ >>> nodelist.update_nodes()
80
+ >>> stm = Hive(node=nodelist.get_hive_nodes())
81
+ >>> account = Account("gtg", blockchain_instance=stm)
82
+ >>> print(account)
83
+ <Account gtg>
84
+ >>> print(account.balances) # doctest: +SKIP
85
+
86
+ .. note:: This class comes with its own caching function to reduce the
87
+ load on the API server. Instances of this class can be
88
+ refreshed with ``Account.refresh()``. The cache can be
89
+ cleared with ``Account.clear_cache()``
90
+
91
+ """
92
+
93
+ type_id = 2
94
+
95
+ def __init__(self, account, full=True, lazy=False, blockchain_instance=None, **kwargs):
96
+ """Initialize an account
97
+
98
+ :param str account: Name of the account
99
+ :param Steem blockchain_instance: Steem
100
+ instance
101
+ :param bool lazy: Use lazy loading
102
+ :param bool full: Obtain all account data including orders, positions,
103
+ etc.
104
+ """
105
+ self.full = full
106
+ self.lazy = lazy
107
+ if blockchain_instance is None:
108
+ if kwargs.get("steem_instance"):
109
+ blockchain_instance = kwargs["steem_instance"]
110
+ elif kwargs.get("hive_instance"):
111
+ blockchain_instance = kwargs["hive_instance"]
112
+ self.blockchain = blockchain_instance or shared_blockchain_instance()
113
+ if isinstance(account, dict):
114
+ account = self._parse_json_data(account)
115
+ super(Account, self).__init__(
116
+ account, lazy=lazy, full=full, id_item="name", blockchain_instance=blockchain_instance
117
+ )
118
+
119
+ def refresh(self):
120
+ """Refresh/Obtain an account's data from the API server"""
121
+ if not self.blockchain.is_connected():
122
+ return
123
+ self.blockchain.rpc.set_next_node_on_empty_reply(self.blockchain.rpc.get_use_appbase())
124
+ if self.blockchain.rpc.get_use_appbase():
125
+ account = self.blockchain.rpc.find_accounts(
126
+ {"accounts": [self.identifier]}, api="database"
127
+ )
128
+ else:
129
+ if self.full:
130
+ account = self.blockchain.rpc.get_accounts([self.identifier], api="database")
131
+ else:
132
+ account = self.blockchain.rpc.lookup_account_names(
133
+ [self.identifier], api="database"
134
+ )
135
+ if self.blockchain.rpc.get_use_appbase() and "accounts" in account:
136
+ account = account["accounts"]
137
+ if account and isinstance(account, list) and len(account) == 1:
138
+ account = account[0]
139
+ if not account:
140
+ raise AccountDoesNotExistsException(self.identifier)
141
+ account = self._parse_json_data(account)
142
+ self.identifier = account["name"]
143
+ # self.blockchain.refresh_data()
144
+
145
+ super(Account, self).__init__(
146
+ account,
147
+ id_item="name",
148
+ lazy=self.lazy,
149
+ full=self.full,
150
+ blockchain_instance=self.blockchain,
151
+ )
152
+
153
+ def _parse_json_data(self, account):
154
+ parse_int = [
155
+ "sbd_seconds",
156
+ "savings_sbd_seconds",
157
+ "average_bandwidth",
158
+ "lifetime_bandwidth",
159
+ "lifetime_market_bandwidth",
160
+ "reputation",
161
+ "withdrawn",
162
+ "to_withdraw",
163
+ "hbd_seconds",
164
+ "savings_hbd_seconds",
165
+ ]
166
+ for p in parse_int:
167
+ if p in account and isinstance(account.get(p), string_types):
168
+ account[p] = int(account.get(p, 0))
169
+ if "proxied_vsf_votes" in account:
170
+ proxied_vsf_votes = []
171
+ for p_int in account["proxied_vsf_votes"]:
172
+ if isinstance(p_int, string_types):
173
+ proxied_vsf_votes.append(int(p_int))
174
+ else:
175
+ proxied_vsf_votes.append(p_int)
176
+ account["proxied_vsf_votes"] = proxied_vsf_votes
177
+ parse_times = [
178
+ "last_owner_update",
179
+ "last_account_update",
180
+ "created",
181
+ "last_owner_proved",
182
+ "last_active_proved",
183
+ "last_account_recovery",
184
+ "last_vote_time",
185
+ "sbd_seconds_last_update",
186
+ "sbd_last_interest_payment",
187
+ "savings_sbd_seconds_last_update",
188
+ "savings_sbd_last_interest_payment",
189
+ "next_vesting_withdrawal",
190
+ "last_market_bandwidth_update",
191
+ "last_post",
192
+ "last_root_post",
193
+ "last_bandwidth_update",
194
+ "hbd_seconds_last_update",
195
+ "hbd_last_interest_payment",
196
+ "savings_hbd_seconds_last_update",
197
+ "savings_hbd_last_interest_payment",
198
+ ]
199
+ for p in parse_times:
200
+ if p in account and isinstance(account.get(p), string_types):
201
+ account[p] = formatTimeString(account.get(p, "1970-01-01T00:00:00"))
202
+ # Parse Amounts
203
+ amounts = [
204
+ "balance",
205
+ "savings_balance",
206
+ "sbd_balance",
207
+ "savings_sbd_balance",
208
+ "reward_sbd_balance",
209
+ "hbd_balance",
210
+ "savings_hbd_balance",
211
+ "reward_hbd_balance",
212
+ "reward_steem_balance",
213
+ "reward_hive_balance",
214
+ "reward_vesting_balance",
215
+ "reward_vesting_steem",
216
+ "vesting_shares",
217
+ "delegated_vesting_shares",
218
+ "received_vesting_shares",
219
+ "vesting_withdraw_rate",
220
+ "vesting_balance",
221
+ ]
222
+ for p in amounts:
223
+ if p in account and isinstance(account.get(p), (string_types, list, dict)):
224
+ account[p] = Amount(account[p], blockchain_instance=self.blockchain)
225
+ return account
226
+
227
+ def json(self):
228
+ output = self.copy()
229
+ parse_int = [
230
+ "sbd_seconds",
231
+ "savings_sbd_seconds",
232
+ "hbd_seconds",
233
+ "savings_hbd_seconds",
234
+ ]
235
+ parse_int_without_zero = [
236
+ "withdrawn",
237
+ "to_withdraw",
238
+ "lifetime_bandwidth",
239
+ "average_bandwidth",
240
+ ]
241
+ for p in parse_int:
242
+ if p in output and isinstance(output[p], integer_types):
243
+ output[p] = str(output[p])
244
+ for p in parse_int_without_zero:
245
+ if p in output and isinstance(output[p], integer_types) and output[p] != 0:
246
+ output[p] = str(output[p])
247
+ if "proxied_vsf_votes" in output:
248
+ proxied_vsf_votes = []
249
+ for p_int in output["proxied_vsf_votes"]:
250
+ if isinstance(p_int, integer_types) and p_int != 0:
251
+ proxied_vsf_votes.append(str(p_int))
252
+ else:
253
+ proxied_vsf_votes.append(p_int)
254
+ output["proxied_vsf_votes"] = proxied_vsf_votes
255
+ parse_times = [
256
+ "last_owner_update",
257
+ "last_account_update",
258
+ "created",
259
+ "last_owner_proved",
260
+ "last_active_proved",
261
+ "last_account_recovery",
262
+ "last_vote_time",
263
+ "sbd_seconds_last_update",
264
+ "sbd_last_interest_payment",
265
+ "savings_sbd_seconds_last_update",
266
+ "savings_sbd_last_interest_payment",
267
+ "next_vesting_withdrawal",
268
+ "last_market_bandwidth_update",
269
+ "last_post",
270
+ "last_root_post",
271
+ "last_bandwidth_update",
272
+ "hbd_seconds_last_update",
273
+ "hbd_last_interest_payment",
274
+ "savings_hbd_seconds_last_update",
275
+ "savings_hbd_last_interest_payment",
276
+ ]
277
+ for p in parse_times:
278
+ if p in output:
279
+ p_date = output.get(p, datetime(1970, 1, 1, 0, 0))
280
+ if isinstance(p_date, (datetime, date, time)):
281
+ output[p] = formatTimeString(p_date)
282
+ else:
283
+ output[p] = p_date
284
+ amounts = [
285
+ "balance",
286
+ "savings_balance",
287
+ "sbd_balance",
288
+ "savings_sbd_balance",
289
+ "reward_sbd_balance",
290
+ "reward_steem_balance",
291
+ "hbd_balance",
292
+ "savings_hbd_balance",
293
+ "reward_hbd_balance",
294
+ "reward_hive_balance",
295
+ "reward_vesting_balance",
296
+ "reward_vesting_steem",
297
+ "vesting_shares",
298
+ "delegated_vesting_shares",
299
+ "received_vesting_shares",
300
+ "vesting_withdraw_rate",
301
+ "vesting_balance",
302
+ ]
303
+ for p in amounts:
304
+ if p in output:
305
+ if p in output:
306
+ output[p] = output.get(p).json()
307
+ return json.loads(str(json.dumps(output)))
308
+
309
+ def getSimilarAccountNames(self, limit=5):
310
+ """Deprecated, please use get_similar_account_names"""
311
+ return self.get_similar_account_names(limit=limit)
312
+
313
+ def get_rc(self):
314
+ """Return RC of account"""
315
+ b = Blockchain(blockchain_instance=self.blockchain)
316
+ return b.find_rc_accounts(self["name"])
317
+
318
+ def get_rc_manabar(self):
319
+ """Returns current_mana and max_mana for RC"""
320
+ rc_param = self.get_rc()
321
+ max_mana = int(rc_param["max_rc"])
322
+ last_mana = int(rc_param["rc_manabar"]["current_mana"])
323
+ last_update_time = rc_param["rc_manabar"]["last_update_time"]
324
+ last_update = datetime.fromtimestamp(last_update_time, tz=timezone.utc)
325
+ diff_in_seconds = (datetime.now(timezone.utc) - last_update).total_seconds()
326
+ current_mana = int(
327
+ last_mana + diff_in_seconds * max_mana / STEEM_VOTING_MANA_REGENERATION_SECONDS
328
+ )
329
+ if current_mana > max_mana:
330
+ current_mana = max_mana
331
+ if max_mana > 0:
332
+ current_pct = current_mana / max_mana * 100
333
+ else:
334
+ current_pct = 0
335
+ max_rc_creation_adjustment = Amount(
336
+ rc_param["max_rc_creation_adjustment"], blockchain_instance=self.blockchain
337
+ )
338
+ return {
339
+ "last_mana": last_mana,
340
+ "last_update_time": last_update_time,
341
+ "current_mana": current_mana,
342
+ "max_mana": max_mana,
343
+ "current_pct": current_pct,
344
+ "max_rc_creation_adjustment": max_rc_creation_adjustment,
345
+ }
346
+
347
+ def get_similar_account_names(self, limit=5):
348
+ """Returns ``limit`` account names similar to the current account
349
+ name as a list
350
+
351
+ :param int limit: limits the number of accounts, which will be
352
+ returned
353
+ :returns: Similar account names as list
354
+ :rtype: list
355
+
356
+ This is a wrapper around :func:`nectar.blockchain.Blockchain.get_similar_account_names()`
357
+ using the current account name as reference.
358
+
359
+ """
360
+ b = Blockchain(blockchain_instance=self.blockchain)
361
+ return b.get_similar_account_names(self.name, limit=limit)
362
+
363
+ @property
364
+ def name(self):
365
+ """Returns the account name"""
366
+ return self["name"]
367
+
368
+ @property
369
+ def profile(self):
370
+ """Returns the account profile"""
371
+ metadata = self.json_metadata
372
+ if "profile" in metadata:
373
+ return metadata["profile"]
374
+ else:
375
+ return {}
376
+
377
+ @property
378
+ def rep(self):
379
+ """Returns the account reputation"""
380
+ return self.get_reputation()
381
+
382
+ @property
383
+ def sp(self):
384
+ """Returns the accounts Steem Power"""
385
+ return self.get_token_power()
386
+
387
+ @property
388
+ def tp(self):
389
+ """Returns the accounts Hive/Steem Power"""
390
+ return self.get_token_power()
391
+
392
+ @property
393
+ def vp(self):
394
+ """Returns the account voting power in the range of 0-100%"""
395
+ return self.get_voting_power()
396
+
397
+ @property
398
+ def json_metadata(self):
399
+ if self["json_metadata"] == "":
400
+ return {}
401
+ return json.loads(self["json_metadata"])
402
+
403
+ @property
404
+ def posting_json_metadata(self):
405
+ if self["posting_json_metadata"] == "":
406
+ return {}
407
+ return json.loads(self["posting_json_metadata"])
408
+
409
+ def print_info(self, force_refresh=False, return_str=False, use_table=False, **kwargs):
410
+ """Prints import information about the account"""
411
+ if force_refresh:
412
+ self.refresh()
413
+ self.blockchain.refresh_data(True)
414
+ bandwidth = self.get_bandwidth()
415
+ if (
416
+ bandwidth is not None
417
+ and bandwidth["allocated"] is not None
418
+ and bandwidth["allocated"] > 0
419
+ ):
420
+ remaining = 100 - bandwidth["used"] / bandwidth["allocated"] * 100
421
+ used_kb = bandwidth["used"] / 1024
422
+ allocated_mb = bandwidth["allocated"] / 1024 / 1024
423
+ last_vote_time_str = formatTimedelta(
424
+ addTzInfo(datetime.now(timezone.utc)) - self["last_vote_time"]
425
+ )
426
+ try:
427
+ rc_mana = self.get_rc_manabar()
428
+ rc = self.get_rc()
429
+ rc_calc = RC(blockchain_instance=self.blockchain)
430
+ except:
431
+ rc_mana = None
432
+ rc_calc = None
433
+
434
+ if use_table:
435
+ t = PrettyTable(["Key", "Value"])
436
+ t.align = "l"
437
+ t.add_row(["Name (rep)", self.name + " (%.2f)" % (self.rep)])
438
+ t.add_row(["Voting Power", "%.2f %%, " % (self.get_voting_power())])
439
+ t.add_row(["Downvoting Power", "%.2f %%, " % (self.get_downvoting_power())])
440
+ t.add_row(["Vote Value", "%.2f $" % (self.get_voting_value_SBD())])
441
+ t.add_row(["Last vote", "%s ago" % last_vote_time_str])
442
+ t.add_row(["Full in ", "%s" % (self.get_recharge_time_str())])
443
+ t.add_row(
444
+ ["Token Power", "%.2f %s" % (self.get_token_power(), self.blockchain.token_symbol)]
445
+ )
446
+ t.add_row(
447
+ [
448
+ "Balance",
449
+ "%s, %s"
450
+ % (str(self.balances["available"][0]), str(self.balances["available"][1])),
451
+ ]
452
+ )
453
+ if (
454
+ False
455
+ and bandwidth is not None
456
+ and bandwidth["allocated"] is not None
457
+ and bandwidth["allocated"] > 0
458
+ ):
459
+ t.add_row(["Remaining Bandwidth", "%.2f %%" % (remaining)])
460
+ t.add_row(
461
+ ["used/allocated Bandwidth", "(%.0f kb of %.0f mb)" % (used_kb, allocated_mb)]
462
+ )
463
+ if rc_mana is not None:
464
+ estimated_rc = int(rc["max_rc"]) * rc_mana["current_pct"] / 100
465
+ t.add_row(["Remaining RC", "%.2f %%" % (rc_mana["current_pct"])])
466
+ t.add_row(
467
+ [
468
+ "Remaining RC",
469
+ "(%.0f G RC of %.0f G RC)"
470
+ % (estimated_rc / 10**9, int(rc["max_rc"]) / 10**9),
471
+ ]
472
+ )
473
+ t.add_row(["Full in ", "%s" % (self.get_manabar_recharge_time_str(rc_mana))])
474
+ t.add_row(["Est. RC for a comment", "%.2f G RC" % (rc_calc.comment() / 10**9)])
475
+ t.add_row(["Est. RC for a vote", "%.2f G RC" % (rc_calc.vote() / 10**9)])
476
+ t.add_row(["Est. RC for a transfer", "%.2f G RC" % (rc_calc.transfer() / 10**9)])
477
+ t.add_row(
478
+ ["Est. RC for a custom_json", "%.2f G RC" % (rc_calc.custom_json() / 10**9)]
479
+ )
480
+
481
+ t.add_row(
482
+ [
483
+ "Comments with current RC",
484
+ "%d comments" % (int(estimated_rc / rc_calc.comment())),
485
+ ]
486
+ )
487
+ t.add_row(
488
+ ["Votes with current RC", "%d votes" % (int(estimated_rc / rc_calc.vote()))]
489
+ )
490
+ t.add_row(
491
+ [
492
+ "Transfer with current RC",
493
+ "%d transfers" % (int(estimated_rc / rc_calc.transfer())),
494
+ ]
495
+ )
496
+ t.add_row(
497
+ [
498
+ "Custom_json with current RC",
499
+ "%d transfers" % (int(estimated_rc / rc_calc.custom_json())),
500
+ ]
501
+ )
502
+
503
+ if return_str:
504
+ return t.get_string(**kwargs)
505
+ else:
506
+ print(t.get_string(**kwargs))
507
+ else:
508
+ ret = self.name + " (%.2f) \n" % (self.rep)
509
+ ret += "--- Voting Power ---\n"
510
+ ret += "%.2f %%, " % (self.get_voting_power())
511
+ ret += " %.2f $\n" % (self.get_voting_value_SBD())
512
+ ret += "full in %s \n" % (self.get_recharge_time_str())
513
+ ret += "--- Downvoting Power ---\n"
514
+ ret += "%.2f %% \n" % (self.get_downvoting_power())
515
+ ret += "--- Balance ---\n"
516
+ ret += "%.2f SP, " % (self.get_token_power())
517
+ ret += "%s, %s\n" % (
518
+ str(self.balances["available"][0]),
519
+ str(self.balances["available"][1]),
520
+ )
521
+ if False and bandwidth["allocated"] > 0:
522
+ ret += "--- Bandwidth ---\n"
523
+ ret += "Remaining: %.2f %%" % (remaining)
524
+ ret += " (%.0f kb of %.0f mb)\n" % (used_kb, allocated_mb)
525
+ if rc_mana is not None:
526
+ estimated_rc = int(rc["max_rc"]) * rc_mana["current_pct"] / 100
527
+ ret += "--- RC manabar ---\n"
528
+ ret += "Remaining: %.2f %%" % (rc_mana["current_pct"])
529
+ ret += " (%.0f G RC of %.0f G RC)\n" % (
530
+ estimated_rc / 10**9,
531
+ int(rc["max_rc"]) / 10**9,
532
+ )
533
+ ret += "full in %s\n" % (self.get_manabar_recharge_time_str(rc_mana))
534
+ ret += "--- Approx Costs ---\n"
535
+ ret += "comment - %.2f G RC - enough RC for %d comments\n" % (
536
+ rc_calc.comment() / 10**9,
537
+ int(estimated_rc / rc_calc.comment()),
538
+ )
539
+ ret += "vote - %.2f G RC - enough RC for %d votes\n" % (
540
+ rc_calc.vote() / 10**9,
541
+ int(estimated_rc / rc_calc.vote()),
542
+ )
543
+ ret += "transfer - %.2f G RC - enough RC for %d transfers\n" % (
544
+ rc_calc.transfer() / 10**9,
545
+ int(estimated_rc / rc_calc.transfer()),
546
+ )
547
+ ret += "custom_json - %.2f G RC - enough RC for %d custom_json\n" % (
548
+ rc_calc.custom_json() / 10**9,
549
+ int(estimated_rc / rc_calc.custom_json()),
550
+ )
551
+ if return_str:
552
+ return ret
553
+ print(ret)
554
+
555
+ def get_reputation(self):
556
+ """Returns the account reputation in the (steemit) normalized form"""
557
+ if not self.blockchain.is_connected():
558
+ return None
559
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
560
+ if self.blockchain.rpc.get_use_appbase():
561
+ try:
562
+ rep = self.blockchain.rpc.get_account_reputations(
563
+ {"account_lower_bound": self["name"], "limit": 1}, api="reputation"
564
+ )["reputations"]
565
+ if len(rep) > 0:
566
+ rep = int(rep[0]["reputation"])
567
+ except:
568
+ if "reputation" in self:
569
+ rep = int(self["reputation"])
570
+ else:
571
+ rep = 0
572
+ else:
573
+ rep = int(self["reputation"])
574
+ return reputation_to_score(rep)
575
+
576
+ def get_manabar(self):
577
+ """Return manabar"""
578
+ max_mana = self.get_effective_vesting_shares()
579
+ if max_mana == 0:
580
+ props = self.blockchain.get_chain_properties()
581
+ required_fee_token = Amount(
582
+ props["account_creation_fee"], blockchain_instance=self.blockchain
583
+ )
584
+ max_mana = int(self.blockchain.token_power_to_vests(required_fee_token))
585
+
586
+ last_mana = int(self["voting_manabar"]["current_mana"])
587
+ last_update_time = self["voting_manabar"]["last_update_time"]
588
+ last_update = datetime.fromtimestamp(last_update_time, tz=timezone.utc)
589
+ diff_in_seconds = (
590
+ addTzInfo(datetime.now(timezone.utc)) - addTzInfo(last_update)
591
+ ).total_seconds()
592
+ current_mana = int(
593
+ last_mana + diff_in_seconds * max_mana / STEEM_VOTING_MANA_REGENERATION_SECONDS
594
+ )
595
+ if current_mana > max_mana:
596
+ current_mana = max_mana
597
+ if max_mana > 0:
598
+ current_mana_pct = current_mana / max_mana * 100
599
+ else:
600
+ current_mana_pct = 0
601
+ return {
602
+ "last_mana": last_mana,
603
+ "last_update_time": last_update_time,
604
+ "current_mana": current_mana,
605
+ "max_mana": max_mana,
606
+ "current_mana_pct": current_mana_pct,
607
+ }
608
+
609
+ def get_downvote_manabar(self):
610
+ """Return downvote manabar"""
611
+ if "downvote_manabar" not in self:
612
+ return None
613
+ max_mana = self.get_effective_vesting_shares() / 4
614
+ if max_mana == 0:
615
+ props = self.blockchain.get_chain_properties()
616
+ required_fee_token = Amount(
617
+ props["account_creation_fee"], blockchain_instance=self.blockchain
618
+ )
619
+ max_mana = int(self.blockchain.token_power_to_vests(required_fee_token) / 4)
620
+
621
+ last_mana = int(self["downvote_manabar"]["current_mana"])
622
+ last_update_time = self["downvote_manabar"]["last_update_time"]
623
+ last_update = datetime.fromtimestamp(last_update_time, tz=timezone.utc)
624
+ diff_in_seconds = (
625
+ addTzInfo(datetime.now(timezone.utc)) - addTzInfo(last_update)
626
+ ).total_seconds()
627
+ current_mana = int(
628
+ last_mana + diff_in_seconds * max_mana / STEEM_VOTING_MANA_REGENERATION_SECONDS
629
+ )
630
+ if current_mana > max_mana:
631
+ current_mana = max_mana
632
+ if max_mana > 0:
633
+ current_mana_pct = current_mana / max_mana * 100
634
+ else:
635
+ current_mana_pct = 0
636
+ return {
637
+ "last_mana": last_mana,
638
+ "last_update_time": last_update_time,
639
+ "current_mana": current_mana,
640
+ "max_mana": max_mana,
641
+ "current_mana_pct": current_mana_pct,
642
+ }
643
+
644
+ def get_voting_power(self, with_regeneration=True):
645
+ """Returns the account voting power in the range of 0-100%
646
+
647
+ :param bool with_regeneration: When True, voting power regeneration is
648
+ included into the result (default True)
649
+ """
650
+ if "voting_manabar" in self:
651
+ manabar = self.get_manabar()
652
+ if with_regeneration:
653
+ total_vp = manabar["current_mana_pct"]
654
+ else:
655
+ if manabar["max_mana"] > 0:
656
+ total_vp = manabar["last_mana"] / manabar["max_mana"] * 100
657
+ else:
658
+ total_vp = 0
659
+ elif "voting_power" in self:
660
+ if with_regeneration:
661
+ last_vote_time = self["last_vote_time"]
662
+ diff_in_seconds = (
663
+ addTzInfo(datetime.now(timezone.utc)) - (last_vote_time)
664
+ ).total_seconds()
665
+ regenerated_vp = (
666
+ diff_in_seconds * STEEM_100_PERCENT / STEEM_VOTE_REGENERATION_SECONDS / 100
667
+ )
668
+ else:
669
+ regenerated_vp = 0
670
+ total_vp = self["voting_power"] / 100 + regenerated_vp
671
+ if total_vp > 100:
672
+ return 100
673
+ if total_vp < 0:
674
+ return 0
675
+ return total_vp
676
+
677
+ def get_downvoting_power(self, with_regeneration=True):
678
+ """Returns the account downvoting power in the range of 0-100%
679
+
680
+ :param bool with_regeneration: When True, downvoting power regeneration is
681
+ included into the result (default True)
682
+ """
683
+ if "downvote_manabar" not in self:
684
+ return 0
685
+
686
+ manabar = self.get_downvote_manabar()
687
+ if with_regeneration:
688
+ total_down_vp = manabar["current_mana_pct"]
689
+ else:
690
+ if manabar["max_mana"] > 0:
691
+ total_down_vp = manabar["last_mana"] / manabar["max_mana"] * 100
692
+ else:
693
+ total_down_vp = 0
694
+ if total_down_vp > 100:
695
+ return 100
696
+ if total_down_vp < 0:
697
+ return 0
698
+ return total_down_vp
699
+
700
+ def get_vests(self, only_own_vests=False):
701
+ """Returns the account vests
702
+
703
+ :param bool only_own_vests: When True, only owned vests is
704
+ returned without delegation (default False)
705
+ """
706
+ vests = self["vesting_shares"]
707
+ if (
708
+ not only_own_vests
709
+ and "delegated_vesting_shares" in self
710
+ and "received_vesting_shares" in self
711
+ ):
712
+ vests = vests - (self["delegated_vesting_shares"]) + (self["received_vesting_shares"])
713
+
714
+ return vests
715
+
716
+ def get_effective_vesting_shares(self):
717
+ """Returns the effective vesting shares"""
718
+ vesting_shares = int(self["vesting_shares"])
719
+ if "delegated_vesting_shares" in self and "received_vesting_shares" in self:
720
+ vesting_shares = (
721
+ vesting_shares
722
+ - int(self["delegated_vesting_shares"])
723
+ + int(self["received_vesting_shares"])
724
+ )
725
+ timestamp = (
726
+ self["next_vesting_withdrawal"] - addTzInfo(datetime(1970, 1, 1))
727
+ ).total_seconds()
728
+ if (
729
+ timestamp > 0
730
+ and "vesting_withdraw_rate" in self
731
+ and "to_withdraw" in self
732
+ and "withdrawn" in self
733
+ ):
734
+ vesting_shares -= min(
735
+ int(self["vesting_withdraw_rate"]),
736
+ int(self["to_withdraw"]) - int(self["withdrawn"]),
737
+ )
738
+ return vesting_shares
739
+
740
+ def get_token_power(self, only_own_vests=False, use_stored_data=True):
741
+ """Returns the account Hive/Steem power (amount of staked token + delegations)
742
+
743
+ :param bool only_own_vests: When True, only owned vests is
744
+ returned without delegation (default False)
745
+ :param bool use_stored_data: When False, an API call returns the current
746
+ vests_to_token_power ratio everytime (default True)
747
+
748
+ """
749
+ return self.blockchain.vests_to_token_power(
750
+ self.get_vests(only_own_vests=only_own_vests), use_stored_data=use_stored_data
751
+ )
752
+
753
+ def get_steem_power(self, onlyOwnSP=False):
754
+ """Returns the account steem power"""
755
+ return self.get_token_power(only_own_vests=onlyOwnSP)
756
+
757
+ def get_voting_value(
758
+ self,
759
+ post_rshares=0,
760
+ voting_weight=100,
761
+ voting_power=None,
762
+ token_power=None,
763
+ not_broadcasted_vote=True,
764
+ ):
765
+ """Returns the account voting value in Hive/Steem token units"""
766
+ if voting_power is None:
767
+ voting_power = self.get_voting_power()
768
+ if token_power is None:
769
+ tp = self.get_token_power()
770
+ else:
771
+ tp = token_power
772
+ voteValue = self.blockchain.token_power_to_token_backed_dollar(
773
+ tp,
774
+ post_rshares=post_rshares,
775
+ voting_power=voting_power * 100,
776
+ vote_pct=voting_weight * 100,
777
+ not_broadcasted_vote=not_broadcasted_vote,
778
+ )
779
+ return voteValue
780
+
781
+ def get_voting_value_SBD(
782
+ self,
783
+ post_rshares=0,
784
+ voting_weight=100,
785
+ voting_power=None,
786
+ steem_power=None,
787
+ not_broadcasted_vote=True,
788
+ ):
789
+ """Returns the account voting value in SBD"""
790
+ return self.get_voting_value(
791
+ post_rshares=post_rshares,
792
+ voting_weight=voting_weight,
793
+ voting_power=voting_power,
794
+ token_power=steem_power,
795
+ not_broadcasted_vote=not_broadcasted_vote,
796
+ )
797
+
798
+ def get_vote_pct_for_SBD(
799
+ self, sbd, post_rshares=0, voting_power=None, steem_power=None, not_broadcasted_vote=True
800
+ ):
801
+ """Returns the voting percentage needed to have a vote worth a given number of SBD.
802
+
803
+ If the returned number is bigger than 10000 or smaller than -10000,
804
+ the given SBD value is too high for that account
805
+
806
+ :param sbd: The amount of SBD in vote value
807
+ :type sbd: str, int, amount.Amount
808
+
809
+ """
810
+ return self.get_vote_pct_for_vote_value(
811
+ sbd,
812
+ post_rshares=post_rshares,
813
+ voting_power=voting_power,
814
+ token_power=steem_power,
815
+ not_broadcasted_vote=not_broadcasted_vote,
816
+ )
817
+
818
+ def get_vote_pct_for_vote_value(
819
+ self,
820
+ token_units,
821
+ post_rshares=0,
822
+ voting_power=None,
823
+ token_power=None,
824
+ not_broadcasted_vote=True,
825
+ ):
826
+ """Returns the voting percentage needed to have a vote worth a given number of Hive/Steem token units
827
+
828
+ If the returned number is bigger than 10000 or smaller than -10000,
829
+ the given SBD value is too high for that account
830
+
831
+ :param token_units: The amount of HBD/SBD in vote value
832
+ :type token_units: str, int, amount.Amount
833
+
834
+ """
835
+ if voting_power is None:
836
+ voting_power = self.get_voting_power()
837
+ if token_power is None:
838
+ token_power = self.get_token_power()
839
+
840
+ if isinstance(token_units, Amount):
841
+ token_units = Amount(token_units, blockchain_instance=self.blockchain)
842
+ elif isinstance(token_units, string_types):
843
+ token_units = Amount(token_units, blockchain_instance=self.blockchain)
844
+ else:
845
+ token_units = Amount(
846
+ token_units,
847
+ self.blockchain.backed_token_symbol,
848
+ blockchain_instance=self.blockchain,
849
+ )
850
+ if token_units["symbol"] != self.blockchain.backed_token_symbol:
851
+ raise AssertionError(
852
+ "Should input %s, not any other asset!" % self.blockchain.backed_token_symbol
853
+ )
854
+ from nectar import Steem
855
+
856
+ if isinstance(self.blockchain, Steem):
857
+ vote_pct = self.blockchain.rshares_to_vote_pct(
858
+ self.blockchain.sbd_to_rshares(
859
+ token_units, not_broadcasted_vote=not_broadcasted_vote
860
+ ),
861
+ post_rshares=post_rshares,
862
+ voting_power=voting_power * 100,
863
+ steem_power=token_power,
864
+ )
865
+ else:
866
+ vote_pct = self.blockchain.rshares_to_vote_pct(
867
+ self.blockchain.hbd_to_rshares(
868
+ token_units, not_broadcasted_vote=not_broadcasted_vote
869
+ ),
870
+ post_rshares=post_rshares,
871
+ voting_power=voting_power * 100,
872
+ hive_power=token_power,
873
+ )
874
+ return vote_pct
875
+
876
+ def get_creator(self):
877
+ """Returns the account creator or `None` if the account was mined"""
878
+ if self["mined"]:
879
+ return None
880
+ ops = list(self.get_account_history(1, 1))
881
+ if not ops or "creator" not in ops[-1]:
882
+ return None
883
+ return ops[-1]["creator"]
884
+
885
+ def get_recharge_time_str(self, voting_power_goal=100, starting_voting_power=None):
886
+ """Returns the account recharge time as string
887
+
888
+ :param float voting_power_goal: voting power goal in percentage (default is 100)
889
+ :param float starting_voting_power: returns recharge time if current voting power is
890
+ the provided value.
891
+
892
+ """
893
+ remainingTime = self.get_recharge_timedelta(
894
+ voting_power_goal=voting_power_goal, starting_voting_power=starting_voting_power
895
+ )
896
+ return formatTimedelta(remainingTime)
897
+
898
+ def get_recharge_timedelta(self, voting_power_goal=100, starting_voting_power=None):
899
+ """Returns the account voting power recharge time as timedelta object
900
+
901
+ :param float voting_power_goal: voting power goal in percentage (default is 100)
902
+ :param float starting_voting_power: returns recharge time if current voting power is
903
+ the provided value.
904
+
905
+ """
906
+ if starting_voting_power is None:
907
+ missing_vp = voting_power_goal - self.get_voting_power()
908
+ elif isinstance(starting_voting_power, int) or isinstance(starting_voting_power, float):
909
+ missing_vp = voting_power_goal - starting_voting_power
910
+ else:
911
+ raise ValueError("starting_voting_power must be a number.")
912
+ if missing_vp < 0:
913
+ return 0
914
+ recharge_seconds = (
915
+ missing_vp * 100 * STEEM_VOTING_MANA_REGENERATION_SECONDS / STEEM_100_PERCENT
916
+ )
917
+ return timedelta(seconds=recharge_seconds)
918
+
919
+ def get_recharge_time(self, voting_power_goal=100, starting_voting_power=None):
920
+ """Returns the account voting power recharge time in minutes
921
+
922
+ :param float voting_power_goal: voting power goal in percentage (default is 100)
923
+ :param float starting_voting_power: returns recharge time if current voting power is
924
+ the provided value.
925
+
926
+ """
927
+ return addTzInfo(datetime.now(timezone.utc)) + self.get_recharge_timedelta(
928
+ voting_power_goal, starting_voting_power
929
+ )
930
+
931
+ def get_manabar_recharge_time_str(self, manabar, recharge_pct_goal=100):
932
+ """Returns the account manabar recharge time as string
933
+
934
+ :param dict manabar: manabar dict from get_manabar() or get_rc_manabar()
935
+ :param float recharge_pct_goal: mana recovery goal in percentage (default is 100)
936
+
937
+ """
938
+ remainingTime = self.get_manabar_recharge_timedelta(
939
+ manabar, recharge_pct_goal=recharge_pct_goal
940
+ )
941
+ return formatTimedelta(remainingTime)
942
+
943
+ def get_manabar_recharge_timedelta(self, manabar, recharge_pct_goal=100):
944
+ """Returns the account mana recharge time as timedelta object
945
+
946
+ :param dict manabar: manabar dict from get_manabar() or get_rc_manabar()
947
+ :param float recharge_pct_goal: mana recovery goal in percentage (default is 100)
948
+
949
+ """
950
+ if "current_mana_pct" in manabar:
951
+ missing_rc_pct = recharge_pct_goal - manabar["current_mana_pct"]
952
+ else:
953
+ missing_rc_pct = recharge_pct_goal - manabar["current_pct"]
954
+ if missing_rc_pct < 0:
955
+ return 0
956
+ recharge_seconds = (
957
+ missing_rc_pct * 100 * STEEM_VOTING_MANA_REGENERATION_SECONDS / STEEM_100_PERCENT
958
+ )
959
+ return timedelta(seconds=recharge_seconds)
960
+
961
+ def get_manabar_recharge_time(self, manabar, recharge_pct_goal=100):
962
+ """Returns the account mana recharge time in minutes
963
+
964
+ :param dict manabar: manabar dict from get_manabar() or get_rc_manabar()
965
+ :param float recharge_pct_goal: mana recovery goal in percentage (default is 100)
966
+
967
+ """
968
+ return addTzInfo(datetime.now(timezone.utc)) + self.get_manabar_recharge_timedelta(
969
+ manabar, recharge_pct_goal
970
+ )
971
+
972
+ def get_feed(
973
+ self, start_entry_id=0, limit=100, raw_data=False, short_entries=False, account=None
974
+ ):
975
+ """Returns a list of items in an account’s feed
976
+
977
+ :param int start_entry_id: default is 0
978
+ :param int limit: default is 100
979
+ :param bool raw_data: default is False
980
+ :param bool short_entries: when set to True and raw_data is True, get_feed_entries is used istead of get_feed
981
+ :param str account: When set, a different account name is used (Default is object account name)
982
+
983
+ :rtype: list
984
+
985
+ .. code-block:: python
986
+
987
+ >>> from nectar.account import Account
988
+ >>> from nectar import Hive
989
+ >>> from nectar.nodelist import NodeList
990
+ >>> nodelist = NodeList()
991
+ >>> nodelist.update_nodes()
992
+ >>> stm = Hive(node=nodelist.get_hive_nodes())
993
+ >>> account = Account("steemit", blockchain_instance=stm)
994
+ >>> account.get_feed(0, 1, raw_data=True)
995
+ []
996
+
997
+ """
998
+ if account is None:
999
+ account = self["name"]
1000
+ account = extract_account_name(account)
1001
+ if not self.blockchain.is_connected():
1002
+ return None
1003
+ from nectar.discussions import Discussions, Query
1004
+
1005
+ d = Discussions(blockchain_instance=self.blockchain)
1006
+ if short_entries:
1007
+ truncate_body = 1
1008
+ else:
1009
+ truncate_body = 0
1010
+ q = Query(limit=limit, tag=account, truncate_body=truncate_body)
1011
+ return [c for c in d.get_discussions("feed", q, limit=limit, raw_data=raw_data)]
1012
+
1013
+ def get_feed_entries(self, start_entry_id=0, limit=100, raw_data=True, account=None):
1014
+ """Returns a list of entries in an account’s feed
1015
+
1016
+ :param int start_entry_id: default is 0
1017
+ :param int limit: default is 100
1018
+ :param bool raw_data: default is False
1019
+ :param bool short_entries: when set to True and raw_data is True, get_feed_entries is used istead of get_feed
1020
+ :param str account: When set, a different account name is used (Default is object account name)
1021
+
1022
+ :rtype: list
1023
+
1024
+ .. code-block:: python
1025
+
1026
+ >>> from nectar.account import Account
1027
+ >>> from nectar import Hive
1028
+ >>> from nectar.nodelist import NodeList
1029
+ >>> nodelist = NodeList()
1030
+ >>> nodelist.update_nodes()
1031
+ >>> stm = Hive(node=nodelist.get_hive_nodes())
1032
+ >>> account = Account("steemit", blockchain_instance=stm)
1033
+ >>> account.get_feed_entries(0, 1)
1034
+ []
1035
+
1036
+ """
1037
+ return self.get_feed(
1038
+ start_entry_id=start_entry_id,
1039
+ limit=limit,
1040
+ raw_data=raw_data,
1041
+ short_entries=True,
1042
+ account=account,
1043
+ )
1044
+
1045
+ def get_blog_entries(self, start_entry_id=0, limit=100, raw_data=True, account=None):
1046
+ """Returns the list of blog entries for an account
1047
+
1048
+ :param int start_entry_id: default is 0
1049
+ :param int limit: default is 100
1050
+ :param bool raw_data: default is False
1051
+ :param str account: When set, a different account name is used (Default is object account name)
1052
+
1053
+ :rtype: list
1054
+
1055
+ .. code-block:: python
1056
+
1057
+ >>> from nectar.account import Account
1058
+ >>> from nectar import Hive
1059
+ >>> from nectar.nodelist import NodeList
1060
+ >>> nodelist = NodeList()
1061
+ >>> nodelist.update_nodes()
1062
+ >>> stm = Hive(node=nodelist.get_hive_nodes())
1063
+ >>> account = Account("steemit", blockchain_instance=stm)
1064
+ >>> entry = account.get_blog_entries(0, 1, raw_data=True)[0]
1065
+ >>> print("%s - %s - %s" % (entry["author"], entry["permlink"], entry["blog"]))
1066
+ steemit - firstpost - steemit
1067
+
1068
+ """
1069
+ return self.get_blog(
1070
+ start_entry_id=start_entry_id,
1071
+ limit=limit,
1072
+ raw_data=raw_data,
1073
+ short_entries=True,
1074
+ account=account,
1075
+ )
1076
+
1077
+ def get_blog(
1078
+ self, start_entry_id=0, limit=100, raw_data=False, short_entries=False, account=None
1079
+ ):
1080
+ """Returns the list of blog entries for an account
1081
+
1082
+ :param int start_entry_id: default is 0
1083
+ :param int limit: default is 100
1084
+ :param bool raw_data: default is False
1085
+ :param bool short_entries: when set to True and raw_data is True, get_blog_entries is used istead of get_blog
1086
+ :param str account: When set, a different account name is used (Default is object account name)
1087
+
1088
+ :rtype: list
1089
+
1090
+ .. code-block:: python
1091
+
1092
+ >>> from nectar.account import Account
1093
+ >>> from nectar import Hive
1094
+ >>> from nectar.nodelist import NodeList
1095
+ >>> nodelist = NodeList()
1096
+ >>> nodelist.update_nodes()
1097
+ >>> stm = Hive(node=nodelist.get_hive_nodes())
1098
+ >>> account = Account("steemit", blockchain_instance=stm)
1099
+ >>> account.get_blog(0, 1)
1100
+ [<Comment @steemit/firstpost>]
1101
+
1102
+ """
1103
+ if account is None:
1104
+ account = self["name"]
1105
+ account = extract_account_name(account)
1106
+
1107
+ if not self.blockchain.is_connected():
1108
+ raise OfflineHasNoRPCException("No RPC available in offline mode!")
1109
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
1110
+ success = True
1111
+ if self.blockchain.rpc.get_use_appbase():
1112
+ try:
1113
+ if raw_data and short_entries:
1114
+ ret = self.blockchain.rpc.get_blog_entries(
1115
+ {"account": account, "start_entry_id": start_entry_id, "limit": limit},
1116
+ api="condenser",
1117
+ )
1118
+ if isinstance(ret, dict) and "blog" in ret:
1119
+ ret = ret["blog"]
1120
+ return [c for c in ret]
1121
+ elif raw_data:
1122
+ ret = self.blockchain.rpc.get_blog(
1123
+ {"account": account, "start_entry_id": start_entry_id, "limit": limit},
1124
+ api="condenser",
1125
+ )
1126
+ if isinstance(ret, dict) and "blog" in ret:
1127
+ ret = ret["blog"]
1128
+ return [c for c in ret]
1129
+ elif not raw_data:
1130
+ from .comment import Comment
1131
+
1132
+ ret = self.blockchain.rpc.get_blog(
1133
+ {"account": account, "start_entry_id": start_entry_id, "limit": limit},
1134
+ api="condenser",
1135
+ )
1136
+ if isinstance(ret, dict) and "blog" in ret:
1137
+ ret = ret["blog"]
1138
+ return [Comment(c["comment"], blockchain_instance=self.blockchain) for c in ret]
1139
+ except:
1140
+ success = False
1141
+
1142
+ if not self.blockchain.rpc.get_use_appbase() or not success:
1143
+ if raw_data and short_entries:
1144
+ return [
1145
+ c
1146
+ for c in self.blockchain.rpc.get_blog_entries(
1147
+ account, start_entry_id, limit, api="condenser"
1148
+ )
1149
+ ]
1150
+
1151
+ elif raw_data:
1152
+ return [
1153
+ c
1154
+ for c in self.blockchain.rpc.get_blog(
1155
+ account, start_entry_id, limit, api="condenser"
1156
+ )
1157
+ ]
1158
+
1159
+ else:
1160
+ from .comment import Comment
1161
+
1162
+ blog_list = self.blockchain.rpc.get_blog(
1163
+ account, start_entry_id, limit, api="condenser"
1164
+ )
1165
+ if blog_list is None:
1166
+ return []
1167
+ return [
1168
+ Comment(c["comment"], blockchain_instance=self.blockchain) for c in blog_list
1169
+ ]
1170
+
1171
+ def get_notifications(self, only_unread=True, limit=100, raw_data=False, account=None):
1172
+ """Returns account notifications
1173
+
1174
+ :param bool only_unread: When True, only unread notfications are shown
1175
+ :param int limit: When set, the number of shown notifications is limited (max limit = 100)
1176
+ :param bool raw_data: When True, the raw data from the api call is returned.
1177
+ :param str account: (optional) the account for which the notification should be received
1178
+ to (defaults to ``default_account``)
1179
+ """
1180
+ if account is None:
1181
+ account = self["name"]
1182
+ account = extract_account_name(account)
1183
+ if not self.blockchain.is_connected():
1184
+ raise OfflineHasNoRPCException("No RPC available in offline mode!")
1185
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
1186
+ if only_unread:
1187
+ unread_notes = self.blockchain.rpc.unread_notifications(
1188
+ {"account": account}, api="bridge"
1189
+ )
1190
+ if limit is None or limit > unread_notes["unread"]:
1191
+ limit = unread_notes["unread"]
1192
+ if limit is None or limit == 0:
1193
+ return []
1194
+ if limit > 100:
1195
+ limit = 100
1196
+ notifications = self.blockchain.rpc.account_notifications(
1197
+ {"account": account, "limit": limit}, api="bridge"
1198
+ )
1199
+ if raw_data:
1200
+ return notifications
1201
+ ret = []
1202
+ for note in notifications:
1203
+ note["date"] = formatTimeString(note["date"])
1204
+ ret.append(note)
1205
+ return ret
1206
+
1207
+ def mark_notifications_as_read(self, last_read=None, account=None):
1208
+ """Broadcast a mark all notification as read custom_json
1209
+
1210
+ :param str last_read: When set, this datestring is used to set the mark as read date
1211
+ :param str account: (optional) the account to broadcast the custom_json
1212
+ to (defaults to ``default_account``)
1213
+
1214
+ """
1215
+ if account is None:
1216
+ account = self["name"]
1217
+ account = extract_account_name(account)
1218
+ if not account:
1219
+ raise ValueError("You need to provide an account")
1220
+ if last_read is None:
1221
+ last_notification = self.get_notifications(only_unread=False, limit=1, account=account)
1222
+ if len(last_notification) == 0:
1223
+ raise ValueError("Notification list is empty")
1224
+ last_read = last_notification[0]["date"]
1225
+ if isinstance(last_read, datetime):
1226
+ last_read = formatTimeString(last_read)
1227
+ json_body = [
1228
+ "setLastRead",
1229
+ {
1230
+ "date": last_read,
1231
+ },
1232
+ ]
1233
+ return self.blockchain.custom_json("notify", json_body, required_posting_auths=[account])
1234
+
1235
+ def get_blog_authors(self, account=None):
1236
+ """Returns a list of authors that have had their content reblogged on a given blog account
1237
+
1238
+ :param str account: When set, a different account name is used (Default is object account name)
1239
+
1240
+ :rtype: list
1241
+
1242
+ .. code-block:: python
1243
+
1244
+ >>> from nectar.account import Account
1245
+ >>> from nectar import Hive
1246
+ >>> from nectar.nodelist import NodeList
1247
+ >>> nodelist = NodeList()
1248
+ >>> nodelist.update_nodes()
1249
+ >>> stm = Hive(node=nodelist.get_hive_nodes())
1250
+ >>> account = Account("gtg", blockchain_instance=stm)
1251
+ >>> account.get_blog_authors() # doctest: +SKIP
1252
+
1253
+ """
1254
+ if account is None:
1255
+ account = self["name"]
1256
+ account = extract_account_name(account)
1257
+ if not self.blockchain.is_connected():
1258
+ raise OfflineHasNoRPCException("No RPC available in offline mode!")
1259
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
1260
+ if self.blockchain.rpc.get_use_appbase():
1261
+ try:
1262
+ return self.blockchain.rpc.get_blog_authors(
1263
+ {"blog_account": account}, api="condenser"
1264
+ )["blog_authors"]
1265
+ except:
1266
+ return self.blockchain.rpc.get_blog_authors(account, api="condenser")
1267
+ else:
1268
+ return self.blockchain.rpc.get_blog_authors(account, api="condenser")
1269
+
1270
+ def get_follow_count(self, account=None):
1271
+ """get_follow_count"""
1272
+ if account is None:
1273
+ account = self["name"]
1274
+ account = extract_account_name(account)
1275
+ if not self.blockchain.is_connected():
1276
+ raise OfflineHasNoRPCException("No RPC available in offline mode!")
1277
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
1278
+ if self.blockchain.rpc.get_use_appbase():
1279
+ try:
1280
+ return self.blockchain.rpc.get_follow_count({"account": account}, api="condenser")
1281
+ except:
1282
+ return self.blockchain.rpc.get_follow_count(account, api="condenser")
1283
+ else:
1284
+ return self.blockchain.rpc.get_follow_count(account, api="condenser")
1285
+
1286
+ def get_followers(self, raw_name_list=True, limit=100):
1287
+ """Returns the account followers as list"""
1288
+ name_list = [x["follower"] for x in self._get_followers(direction="follower", limit=limit)]
1289
+ if raw_name_list:
1290
+ return name_list
1291
+ else:
1292
+ return Accounts(name_list, blockchain_instance=self.blockchain)
1293
+
1294
+ def get_following(self, raw_name_list=True, limit=100):
1295
+ """Returns who the account is following as list"""
1296
+ name_list = [
1297
+ x["following"] for x in self._get_followers(direction="following", limit=limit)
1298
+ ]
1299
+ if raw_name_list:
1300
+ return name_list
1301
+ else:
1302
+ return Accounts(name_list, blockchain_instance=self.blockchain)
1303
+
1304
+ def get_muters(self, raw_name_list=True, limit=100):
1305
+ """Returns the account muters as list"""
1306
+ name_list = [
1307
+ x["follower"]
1308
+ for x in self._get_followers(direction="follower", what="ignore", limit=limit)
1309
+ ]
1310
+ if raw_name_list:
1311
+ return name_list
1312
+ else:
1313
+ return Accounts(name_list, blockchain_instance=self.blockchain)
1314
+
1315
+ def get_mutings(self, raw_name_list=True, limit=100):
1316
+ """Returns who the account is muting as list"""
1317
+ name_list = [
1318
+ x["following"]
1319
+ for x in self._get_followers(direction="following", what="ignore", limit=limit)
1320
+ ]
1321
+ if raw_name_list:
1322
+ return name_list
1323
+ else:
1324
+ return Accounts(name_list, blockchain_instance=self.blockchain)
1325
+
1326
+ def get_follow_list(self, follow_type, starting_account=None, limit=100, raw_name_list=True):
1327
+ """Returns the follow list for the specified follow_type (Only HIVE with HF >= 24)
1328
+
1329
+ :param list follow_type: follow_type can be `blacklisted`, `follow_blacklist` `muted`, or `follow_muted`
1330
+ """
1331
+ if not self.blockchain.is_connected():
1332
+ raise OfflineHasNoRPCException("No RPC available in offline mode!")
1333
+ limit_reached = True
1334
+ cnt = 0
1335
+ while limit_reached:
1336
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
1337
+ query = {
1338
+ "observer": self.name,
1339
+ "follow_type": follow_type,
1340
+ "starting_account": starting_account,
1341
+ "limit": limit,
1342
+ }
1343
+ followers = self.blockchain.rpc.get_follow_list(query, api="bridge")
1344
+ if cnt == 0:
1345
+ name_list = followers
1346
+ elif followers is not None and len(followers) > 1:
1347
+ name_list += followers[1:]
1348
+ if followers is not None and len(followers) >= limit:
1349
+ starting_account = followers[-1]
1350
+ limit_reached = True
1351
+ cnt += 1
1352
+ else:
1353
+ limit_reached = False
1354
+ if raw_name_list:
1355
+ return name_list
1356
+ else:
1357
+ return Accounts(name_list, blockchain_instance=self.blockchain)
1358
+
1359
+ def _get_followers(self, direction="follower", last_user="", what="blog", limit=100):
1360
+ """Help function, used in get_followers and get_following"""
1361
+ if not self.blockchain.is_connected():
1362
+ raise OfflineHasNoRPCException("No RPC available in offline mode!")
1363
+ followers_list = []
1364
+ limit_reached = True
1365
+ cnt = 0
1366
+ while limit_reached:
1367
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
1368
+ if self.blockchain.rpc.get_use_appbase():
1369
+ query = {"account": self.name, "start": last_user, "type": what, "limit": limit}
1370
+ if direction == "follower":
1371
+ try:
1372
+ followers = self.blockchain.rpc.get_followers(query, api="condenser")
1373
+ except:
1374
+ followers = self.blockchain.rpc.get_followers(
1375
+ self.name, last_user, what, limit, api="condenser"
1376
+ )
1377
+ if isinstance(followers, dict) and "followers" in followers:
1378
+ followers = followers["followers"]
1379
+ elif direction == "following":
1380
+ try:
1381
+ followers = self.blockchain.rpc.get_following(query, api="condenser")
1382
+ except:
1383
+ followers = self.blockchain.rpc.get_following(
1384
+ self.name, last_user, what, limit, api="condenser"
1385
+ )
1386
+ if isinstance(followers, dict) and "following" in followers:
1387
+ followers = followers["following"]
1388
+ else:
1389
+ if direction == "follower":
1390
+ try:
1391
+ followers = self.blockchain.rpc.get_followers(
1392
+ self.name, last_user, what, limit, api="condenser"
1393
+ )
1394
+ except:
1395
+ followers = self.blockchain.rpc.get_followers(
1396
+ [self.name, last_user, what, limit], api="condenser"
1397
+ )
1398
+ elif direction == "following":
1399
+ try:
1400
+ followers = self.blockchain.rpc.get_following(
1401
+ self.name, last_user, what, limit, api="condenser"
1402
+ )
1403
+ except:
1404
+ followers = self.blockchain.rpc.get_following(
1405
+ self.name, last_user, what, limit, api="condenser"
1406
+ )
1407
+ if cnt == 0:
1408
+ followers_list = followers
1409
+ elif followers is not None and len(followers) > 1:
1410
+ followers_list += followers[1:]
1411
+ if followers is not None and len(followers) >= limit:
1412
+ last_user = followers[-1][direction]
1413
+ limit_reached = True
1414
+ cnt += 1
1415
+ else:
1416
+ limit_reached = False
1417
+
1418
+ return followers_list
1419
+
1420
+ def list_all_subscriptions(self, account=None):
1421
+ """Returns all subscriptions"""
1422
+ if account is None:
1423
+ account = self["name"]
1424
+ account = extract_account_name(account)
1425
+ if not self.blockchain.is_connected():
1426
+ raise OfflineHasNoRPCException("No RPC available in offline mode!")
1427
+ self.blockchain.rpc.set_next_node_on_empty_reply(True)
1428
+ return self.blockchain.rpc.list_all_subscriptions({"account": account}, api="bridge")
1429
+
1430
+ def get_account_posts(self, sort="feed", limit=20, account=None, observer=None, raw_data=False):
1431
+ """Returns account feed"""
1432
+ if account is None:
1433
+ account = self["name"]
1434
+ account = extract_account_name(account)
1435
+ if observer is None:
1436
+ observer = account
1437
+ if not self.blockchain.is_connected():
1438
+ raise OfflineHasNoRPCException("No RPC available in offline mode!")
1439
+ from nectar.comment import AccountPosts
1440
+
1441
+ return AccountPosts(sort, account, observer=observer, limit=limit, raw_data=raw_data)
1442
+
1443
+ @property
1444
+ def available_balances(self):
1445
+ """List balances of an account. This call returns instances of
1446
+ :class:`nectar.amount.Amount`.
1447
+ """
1448
+ if "sbd_balance" in self:
1449
+ amount_list = ["balance", "sbd_balance", "vesting_shares"]
1450
+ elif "hbd_balance" in self:
1451
+ amount_list = ["balance", "hbd_balance", "vesting_shares"]
1452
+ else:
1453
+ amount_list = ["balance", "vesting_shares"]
1454
+ available_amount = []
1455
+ for amount in amount_list:
1456
+ if amount in self:
1457
+ available_amount.append(self[amount].copy())
1458
+ return available_amount
1459
+
1460
+ @property
1461
+ def saving_balances(self):
1462
+ savings_amount = []
1463
+ if "savings_sbd_balance" in self:
1464
+ amount_list = ["savings_balance", "savings_sbd_balance"]
1465
+ elif "savings_hbd_balance" in self:
1466
+ amount_list = ["savings_balance", "savings_hbd_balance"]
1467
+ else:
1468
+ amount_list = ["savings_balance"]
1469
+ for amount in amount_list:
1470
+ if amount in self:
1471
+ savings_amount.append(self[amount].copy())
1472
+ return savings_amount
1473
+
1474
+ @property
1475
+ def reward_balances(self):
1476
+ if "reward_steem_balance" in self and "reward_sbd_balance" in self:
1477
+ amount_list = ["reward_steem_balance", "reward_sbd_balance", "reward_vesting_balance"]
1478
+ elif "reward_hive_balance" in self and "reward_hbd_balance" in self:
1479
+ amount_list = ["reward_hive_balance", "reward_hbd_balance", "reward_vesting_balance"]
1480
+ else:
1481
+ amount_list = []
1482
+ rewards_amount = []
1483
+ for amount in amount_list:
1484
+ if amount in self:
1485
+ rewards_amount.append(self[amount].copy())
1486
+ return rewards_amount
1487
+
1488
+ @property
1489
+ def total_balances(self):
1490
+ symbols = []
1491
+ for balance in self.available_balances:
1492
+ symbols.append(balance["symbol"])
1493
+ ret = []
1494
+ for i in range(len(symbols)):
1495
+ balance_sum = self.get_balance(self.available_balances, symbols[i])
1496
+ balance_sum = balance_sum + self.get_balance(self.saving_balances, symbols[i])
1497
+ balance_sum = balance_sum + self.get_balance(self.reward_balances, symbols[i])
1498
+ ret.append(balance_sum)
1499
+ return ret
1500
+
1501
+ @property
1502
+ def balances(self):
1503
+ """Returns all account balances as dictionary"""
1504
+ return self.get_balances()
1505
+
1506
+ def get_balances(self):
1507
+ """Returns all account balances as dictionary
1508
+
1509
+ :returns: Account balances
1510
+ :rtype: dictionary
1511
+
1512
+ Sample output:
1513
+
1514
+ .. code-block:: js
1515
+
1516
+ {
1517
+ 'available': [102.985 STEEM, 0.008 SBD, 146273.695970 VESTS],
1518
+ 'savings': [0.000 STEEM, 0.000 SBD],
1519
+ 'rewards': [0.000 STEEM, 0.000 SBD, 0.000000 VESTS],
1520
+ 'total': [102.985 STEEM, 0.008 SBD, 146273.695970 VESTS]
1521
+ }
1522
+
1523
+ """
1524
+ return {
1525
+ "available": self.available_balances,
1526
+ "savings": self.saving_balances,
1527
+ "rewards": self.reward_balances,
1528
+ "total": self.total_balances,
1529
+ }
1530
+
1531
+ def get_balance(self, balances, symbol):
1532
+ """Obtain the balance of a specific Asset. This call returns instances of
1533
+ :class:`nectar.amount.Amount`. Available balance types:
1534
+
1535
+ * "available"
1536
+ * "saving"
1537
+ * "reward"
1538
+ * "total"
1539
+
1540
+ :param str balances: Defines the balance type
1541
+ :param symbol: Can be "SBD", "STEEM" or "VESTS
1542
+ :type symbol: str, dict
1543
+
1544
+ .. code-block:: python
1545
+
1546
+ >>> from nectar.account import Account
1547
+ >>> from nectar import Hive
1548
+ >>> from nectar.nodelist import NodeList
1549
+ >>> nodelist = NodeList()
1550
+ >>> nodelist.update_nodes()
1551
+ >>> stm = Hive(node=nodelist.get_hive_nodes())
1552
+ >>> account = Account("nectar.app", blockchain_instance=stm)
1553
+ >>> account.get_balance("rewards", "HBD")
1554
+ 0.000 HBD
1555
+
1556
+ """
1557
+ if isinstance(balances, string_types):
1558
+ if balances == "available":
1559
+ balances = self.available_balances
1560
+ elif balances == "savings":
1561
+ balances = self.saving_balances
1562
+ elif balances == "rewards":
1563
+ balances = self.reward_balances
1564
+ elif balances == "total":
1565
+ balances = self.total_balances
1566
+ else:
1567
+ return
1568
+
1569
+ if isinstance(symbol, dict) and "symbol" in symbol:
1570
+ symbol = symbol["symbol"]
1571
+
1572
+ for b in balances:
1573
+ if b["symbol"] == symbol:
1574
+ return b
1575
+ from .amount import Amount
1576
+
1577
+ return Amount(0, symbol, blockchain_instance=self.blockchain)
1578
+
1579
+ def interest(self):
1580
+ """Calculate interest for an account
1581
+
1582
+ :param str account: Account name to get interest for
1583
+ :rtype: dictionary
1584
+
1585
+ Sample output:
1586
+
1587
+ .. code-block:: js
1588
+
1589
+ {
1590
+ 'interest': 0.0,
1591
+ 'last_payment': datetime.datetime(2018, 1, 26, 5, 50, 27, tzinfo=<UTC>),
1592
+ 'next_payment': datetime.datetime(2018, 2, 25, 5, 50, 27, tzinfo=<UTC>),
1593
+ 'next_payment_duration': datetime.timedelta(-65, 52132, 684026),
1594
+ 'interest_rate': 0.0
1595
+ }
1596
+
1597
+ """
1598
+ interest_amount = 0
1599
+ interest_rate = 0
1600
+ next_payment = datetime(1970, 1, 1, 0, 0, 0)
1601
+ last_payment = datetime(1970, 1, 1, 0, 0, 0)
1602
+ if "sbd_last_interest_payment" in self:
1603
+ last_payment = self["sbd_last_interest_payment"]
1604
+ next_payment = last_payment + timedelta(days=30)
1605
+ interest_rate = (
1606
+ self.blockchain.get_dynamic_global_properties()["sbd_interest_rate"] / 100
1607
+ ) # percent
1608
+ interest_amount = (
1609
+ (interest_rate / 100)
1610
+ * int(int(self["sbd_seconds"]) / (60 * 60 * 24 * 356))
1611
+ * 10**-3
1612
+ )
1613
+ elif "hbd_last_interest_payment" in self:
1614
+ last_payment = self["hbd_last_interest_payment"]
1615
+ next_payment = last_payment + timedelta(days=30)
1616
+ interest_rate = (
1617
+ self.blockchain.get_dynamic_global_properties()["hbd_interest_rate"] / 100
1618
+ ) # percent
1619
+ interest_amount = (
1620
+ (interest_rate / 100)
1621
+ * int(int(self["hbd_seconds"]) / (60 * 60 * 24 * 356))
1622
+ * 10**-3
1623
+ )
1624
+ return {
1625
+ "interest": interest_amount,
1626
+ "last_payment": last_payment,
1627
+ "next_payment": next_payment,
1628
+ "next_payment_duration": next_payment - addTzInfo(datetime.now(timezone.utc)),
1629
+ "interest_rate": interest_rate,
1630
+ }
1631
+
1632
+ @property
1633
+ def is_fully_loaded(self):
1634
+ """Is this instance fully loaded / e.g. all data available?
1635
+
1636
+ :rtype: bool
1637
+ """
1638
+ return self.full
1639
+
1640
+ def ensure_full(self):
1641
+ """Ensure that all data are loaded"""
1642
+ if not self.is_fully_loaded:
1643
+ self.full = True
1644
+ self.refresh()
1645
+
1646
+ def get_account_bandwidth(self, bandwidth_type=1, account=None):
1647
+ """get_account_bandwidth"""
1648
+ if account is None:
1649
+ account = self["name"]
1650
+ account = extract_account_name(account)
1651
+ if not self.blockchain.is_connected():
1652
+ raise OfflineHasNoRPCException("No RPC available in offline mode!")
1653
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
1654
+ return self.blockchain.rpc.get_account_bandwidth(account, bandwidth_type)
1655
+
1656
+ def get_bandwidth(self):
1657
+ """Returns used and allocated bandwidth
1658
+
1659
+ :rtype: dictionary
1660
+
1661
+ Sample output:
1662
+
1663
+ .. code-block:: js
1664
+
1665
+ {
1666
+ 'used': 0,
1667
+ 'allocated': 2211037
1668
+ }
1669
+
1670
+ """
1671
+ account = self["name"]
1672
+ global_properties = self.blockchain.get_dynamic_global_properties()
1673
+ try:
1674
+ reserve_ratio = self.blockchain.get_reserve_ratio()
1675
+ except:
1676
+ return {"used": 0, "allocated": 0}
1677
+ if "received_vesting_shares" in self:
1678
+ received_vesting_shares = self["received_vesting_shares"].amount
1679
+ else:
1680
+ received_vesting_shares = 0
1681
+ vesting_shares = self["vesting_shares"].amount
1682
+ if reserve_ratio is None or reserve_ratio["max_virtual_bandwidth"] is None:
1683
+ return {"used": None, "allocated": None}
1684
+ max_virtual_bandwidth = float(reserve_ratio["max_virtual_bandwidth"])
1685
+ total_vesting_shares = Amount(
1686
+ global_properties["total_vesting_shares"], blockchain_instance=self.blockchain
1687
+ ).amount
1688
+ allocated_bandwidth = (
1689
+ max_virtual_bandwidth
1690
+ * (vesting_shares + received_vesting_shares)
1691
+ / total_vesting_shares
1692
+ )
1693
+ allocated_bandwidth = round(allocated_bandwidth / 1000000)
1694
+
1695
+ if self.blockchain.is_connected() and self.blockchain.rpc.get_use_appbase():
1696
+ try:
1697
+ account_bandwidth = self.get_account_bandwidth(bandwidth_type=1, account=account)
1698
+ except:
1699
+ account_bandwidth = None
1700
+ if account_bandwidth is None:
1701
+ return {"used": 0, "allocated": allocated_bandwidth}
1702
+ last_bandwidth_update = formatTimeString(account_bandwidth["last_bandwidth_update"])
1703
+ average_bandwidth = float(account_bandwidth["average_bandwidth"])
1704
+ else:
1705
+ last_bandwidth_update = self["last_bandwidth_update"]
1706
+ average_bandwidth = float(self["average_bandwidth"])
1707
+ total_seconds = 604800
1708
+
1709
+ seconds_since_last_update = addTzInfo(datetime.now(timezone.utc)) - last_bandwidth_update
1710
+ seconds_since_last_update = seconds_since_last_update.total_seconds()
1711
+ used_bandwidth = 0
1712
+ if seconds_since_last_update < total_seconds:
1713
+ used_bandwidth = (
1714
+ (total_seconds - seconds_since_last_update) * average_bandwidth
1715
+ ) / total_seconds
1716
+ used_bandwidth = round(used_bandwidth / 1000000)
1717
+
1718
+ return {"used": used_bandwidth, "allocated": allocated_bandwidth}
1719
+ # print("bandwidth percent used: " + str(100 * used_bandwidth / allocated_bandwidth))
1720
+ # print("bandwidth percent remaining: " + str(100 - (100 * used_bandwidth / allocated_bandwidth)))
1721
+
1722
+ def get_owner_history(self, account=None):
1723
+ """Returns the owner history of an account.
1724
+
1725
+ :param str account: When set, a different account is used for the request (Default is object account name)
1726
+
1727
+ :rtype: list
1728
+
1729
+ .. code-block:: python
1730
+
1731
+ >>> from nectar.account import Account
1732
+ >>> from nectar import Hive
1733
+ >>> from nectar.nodelist import NodeList
1734
+ >>> nodelist = NodeList()
1735
+ >>> nodelist.update_nodes()
1736
+ >>> stm = Hive(node=nodelist.get_hive_nodes())
1737
+ >>> account = Account("nectar.app", blockchain_instance=stm)
1738
+ >>> account.get_owner_history()
1739
+ []
1740
+
1741
+ """
1742
+ if account is None:
1743
+ account = self["name"]
1744
+ account = extract_account_name(account)
1745
+ if not self.blockchain.is_connected():
1746
+ raise OfflineHasNoRPCException("No RPC available in offline mode!")
1747
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
1748
+ if self.blockchain.rpc.get_use_appbase():
1749
+ return self.blockchain.rpc.find_owner_histories({"owner": account}, api="database")[
1750
+ "owner_auths"
1751
+ ]
1752
+ else:
1753
+ return self.blockchain.rpc.get_owner_history(account)
1754
+
1755
+ def get_conversion_requests(self, account=None):
1756
+ """Returns a list of SBD conversion request
1757
+
1758
+ :param str account: When set, a different account is used for the request (Default is object account name)
1759
+
1760
+ :rtype: list
1761
+
1762
+ .. code-block:: python
1763
+
1764
+ >>> from nectar.account import Account
1765
+ >>> from nectar import Hive
1766
+ >>> from nectar.nodelist import NodeList
1767
+ >>> nodelist = NodeList()
1768
+ >>> nodelist.update_nodes()
1769
+ >>> stm = Hive(node=nodelist.get_hive_nodes())
1770
+ >>> account = Account("nectar.app", blockchain_instance=stm)
1771
+ >>> account.get_conversion_requests()
1772
+ []
1773
+
1774
+ """
1775
+ if account is None:
1776
+ account = self["name"]
1777
+ account = extract_account_name(account)
1778
+ if not self.blockchain.is_connected():
1779
+ raise OfflineHasNoRPCException("No RPC available in offline mode!")
1780
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
1781
+ if self.blockchain.rpc.get_use_appbase() and "sbd_balance" in self:
1782
+ return self.blockchain.rpc.find_sbd_conversion_requests(
1783
+ {"account": account}, api="database"
1784
+ )["requests"]
1785
+ elif self.blockchain.rpc.get_use_appbase() and "hbd_balance" in self:
1786
+ return self.blockchain.rpc.find_hbd_conversion_requests(
1787
+ {"account": account}, api="database"
1788
+ )["requests"]
1789
+ else:
1790
+ return self.blockchain.rpc.get_conversion_requests(account)
1791
+
1792
+ def get_vesting_delegations(self, start_account="", limit=100, account=None):
1793
+ """Returns the vesting delegations by an account.
1794
+
1795
+ :param str account: When set, a different account is used for the request (Default is object account name)
1796
+ :param str start_account: delegatee to start with, leave empty to start from the first by name
1797
+ :param int limit: maximum number of results to return
1798
+ :rtype: list
1799
+
1800
+ .. code-block:: python
1801
+
1802
+ >>> from nectar.account import Account
1803
+ >>> from nectar import Hive
1804
+ >>> from nectar.nodelist import NodeList
1805
+ >>> nodelist = NodeList()
1806
+ >>> nodelist.update_nodes()
1807
+ >>> stm = Hive(node=nodelist.get_hive_nodes())
1808
+ >>> account = Account("nectar.app", blockchain_instance=stm)
1809
+ >>> account.get_vesting_delegations()
1810
+ []
1811
+
1812
+ """
1813
+ if account is None:
1814
+ account = self["name"]
1815
+ account = extract_account_name(account)
1816
+ if not self.blockchain.is_connected():
1817
+ raise OfflineHasNoRPCException("No RPC available in offline mode!")
1818
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
1819
+ if self.blockchain.rpc.get_use_appbase():
1820
+ delegations = self.blockchain.rpc.list_vesting_delegations(
1821
+ {"start": [account, start_account], "limit": limit, "order": "by_delegation"},
1822
+ api="database",
1823
+ )["delegations"]
1824
+ return [d for d in delegations if d["delegator"] == account]
1825
+ else:
1826
+ return self.blockchain.rpc.get_vesting_delegations(account, start_account, limit)
1827
+
1828
+ def get_withdraw_routes(self, account=None):
1829
+ """Returns the withdraw routes for an account.
1830
+
1831
+ :param str account: When set, a different account is used for the request (Default is object account name)
1832
+
1833
+ :rtype: list
1834
+
1835
+ .. code-block:: python
1836
+
1837
+ >>> from nectar.account import Account
1838
+ >>> from nectar import Hive
1839
+ >>> from nectar.nodelist import NodeList
1840
+ >>> nodelist = NodeList()
1841
+ >>> nodelist.update_nodes()
1842
+ >>> stm = Hive(node=nodelist.get_hive_nodes())
1843
+ >>> account = Account("nectar.app", blockchain_instance=stm)
1844
+ >>> account.get_withdraw_routes()
1845
+ []
1846
+
1847
+ """
1848
+ if account is None:
1849
+ account = self["name"]
1850
+ account = extract_account_name(account)
1851
+ if not self.blockchain.is_connected():
1852
+ raise OfflineHasNoRPCException("No RPC available in offline mode!")
1853
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
1854
+ if self.blockchain.rpc.get_use_appbase():
1855
+ return self.blockchain.rpc.find_withdraw_vesting_routes(
1856
+ {"account": account, "order": "by_withdraw_route"}, api="database"
1857
+ )["routes"]
1858
+ else:
1859
+ return self.blockchain.rpc.get_withdraw_routes(account, "all")
1860
+
1861
+ def get_savings_withdrawals(self, direction="from", account=None):
1862
+ """Returns the list of savings withdrawls for an account.
1863
+
1864
+ :param str account: When set, a different account is used for the request (Default is object account name)
1865
+ :param str direction: Can be either from or to (only non appbase nodes)
1866
+
1867
+ :rtype: list
1868
+
1869
+ .. code-block:: python
1870
+
1871
+ >>> from nectar.account import Account
1872
+ >>> from nectar import Hive
1873
+ >>> from nectar.nodelist import NodeList
1874
+ >>> nodelist = NodeList()
1875
+ >>> nodelist.update_nodes()
1876
+ >>> stm = Hive(node=nodelist.get_hive_nodes())
1877
+ >>> account = Account("nectar.app", blockchain_instance=stm)
1878
+ >>> account.get_savings_withdrawals()
1879
+ []
1880
+
1881
+ """
1882
+ if account is None:
1883
+ account = self["name"]
1884
+ account = extract_account_name(account)
1885
+ if not self.blockchain.is_connected():
1886
+ raise OfflineHasNoRPCException("No RPC available in offline mode!")
1887
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
1888
+ if self.blockchain.rpc.get_use_appbase():
1889
+ return self.blockchain.rpc.find_savings_withdrawals(
1890
+ {"account": account}, api="database"
1891
+ )["withdrawals"]
1892
+ elif direction == "from":
1893
+ return self.blockchain.rpc.get_savings_withdraw_from(account)
1894
+ elif direction == "to":
1895
+ return self.blockchain.rpc.get_savings_withdraw_to(account)
1896
+
1897
+ def get_recovery_request(self, account=None):
1898
+ """Returns the recovery request for an account
1899
+
1900
+ :param str account: When set, a different account is used for the request (Default is object account name)
1901
+
1902
+ :rtype: list
1903
+
1904
+ .. code-block:: python
1905
+
1906
+ >>> from nectar.account import Account
1907
+ >>> from nectar import Hive
1908
+ >>> from nectar.nodelist import NodeList
1909
+ >>> nodelist = NodeList()
1910
+ >>> nodelist.update_nodes()
1911
+ >>> stm = Hive(node=nodelist.get_hive_nodes())
1912
+ >>> account = Account("nectar.app", blockchain_instance=stm)
1913
+ >>> account.get_recovery_request()
1914
+ []
1915
+
1916
+ """
1917
+ if account is None:
1918
+ account = self["name"]
1919
+ account = extract_account_name(account)
1920
+ if not self.blockchain.is_connected():
1921
+ raise OfflineHasNoRPCException("No RPC available in offline mode!")
1922
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
1923
+ if self.blockchain.rpc.get_use_appbase():
1924
+ return self.blockchain.rpc.find_account_recovery_requests(
1925
+ {"account": account}, api="database"
1926
+ )["requests"]
1927
+ else:
1928
+ return self.blockchain.rpc.get_recovery_request(account)
1929
+
1930
+ def get_escrow(self, escrow_id=0, account=None):
1931
+ """Returns the escrow for a certain account by id
1932
+
1933
+ :param int escrow_id: Id (only pre appbase)
1934
+ :param str account: When set, a different account is used for the request (Default is object account name)
1935
+
1936
+ :rtype: list
1937
+
1938
+ .. code-block:: python
1939
+
1940
+ >>> from nectar.account import Account
1941
+ >>> from nectar import Hive
1942
+ >>> from nectar.nodelist import NodeList
1943
+ >>> nodelist = NodeList()
1944
+ >>> nodelist.update_nodes()
1945
+ >>> stm = Hive(node=nodelist.get_hive_nodes())
1946
+ >>> account = Account("nectar.app", blockchain_instance=stm)
1947
+ >>> account.get_escrow(1234)
1948
+ []
1949
+
1950
+ """
1951
+ if account is None:
1952
+ account = self["name"]
1953
+ account = extract_account_name(account)
1954
+ if not self.blockchain.is_connected():
1955
+ raise OfflineHasNoRPCException("No RPC available in offline mode!")
1956
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
1957
+ if self.blockchain.rpc.get_use_appbase():
1958
+ return self.blockchain.rpc.find_escrows({"from": account}, api="database")["escrows"]
1959
+ else:
1960
+ return self.blockchain.rpc.get_escrow(account, escrow_id)
1961
+
1962
+ def verify_account_authority(self, keys, account=None):
1963
+ """Returns true if the signers have enough authority to authorize an account.
1964
+
1965
+ :param list keys: public key
1966
+ :param str account: When set, a different account is used for the request (Default is object account name)
1967
+
1968
+ :rtype: dictionary
1969
+
1970
+ .. code-block:: python
1971
+
1972
+ >>> from nectar.account import Account
1973
+ >>> from nectar import Hive
1974
+ >>> from nectar.nodelist import NodeList
1975
+ >>> nodelist = NodeList()
1976
+ >>> nodelist.update_nodes()
1977
+ >>> stm = Hive(node=nodelist.get_hive_nodes())
1978
+ >>> account = Account("steemit", blockchain_instance=stm)
1979
+ >>> print(account.verify_account_authority(["STM7Q2rLBqzPzFeteQZewv9Lu3NLE69fZoLeL6YK59t7UmssCBNTU"])["valid"])
1980
+ False
1981
+
1982
+ """
1983
+ if account is None:
1984
+ account = self["name"]
1985
+ account = extract_account_name(account)
1986
+ if not self.blockchain.is_connected():
1987
+ raise OfflineHasNoRPCException("No RPC available in offline mode!")
1988
+ if not isinstance(keys, list):
1989
+ keys = [keys]
1990
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
1991
+ try:
1992
+ if self.blockchain.rpc.get_use_appbase():
1993
+ return self.blockchain.rpc.verify_account_authority(
1994
+ {"account": account, "signers": keys}, api="database"
1995
+ )
1996
+ else:
1997
+ return self.blockchain.rpc.verify_account_authority(account, keys)
1998
+ except MissingRequiredActiveAuthority:
1999
+ return {"valid": False}
2000
+
2001
+ def get_tags_used_by_author(self, account=None):
2002
+ """Returns a list of tags used by an author.
2003
+
2004
+ :param str account: When set, a different account is used for the request (Default is object account name)
2005
+
2006
+ :rtype: list
2007
+
2008
+ """
2009
+ if account is None:
2010
+ account = self["name"]
2011
+ account = extract_account_name(account)
2012
+ if not self.blockchain.is_connected():
2013
+ raise OfflineHasNoRPCException("No RPC available in offline mode!")
2014
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
2015
+ if self.blockchain.rpc.get_use_appbase():
2016
+ return self.blockchain.rpc.get_tags_used_by_author(
2017
+ {"author": account}, api="condenser"
2018
+ )["tags"]
2019
+ else:
2020
+ return self.blockchain.rpc.get_tags_used_by_author(account, api="condenser")
2021
+
2022
+ def get_expiring_vesting_delegations(self, after=None, limit=1000, account=None):
2023
+ """Returns the expirations for vesting delegations.
2024
+
2025
+ :param datetime after: expiration after (only for pre appbase nodes)
2026
+ :param int limit: limits number of shown entries (only for pre appbase nodes)
2027
+ :param str account: When set, a different account is used for the request (Default is object account name)
2028
+
2029
+ :rtype: list
2030
+
2031
+ .. code-block:: python
2032
+
2033
+ >>> from nectar.account import Account
2034
+ >>> from nectar import Hive
2035
+ >>> from nectar.nodelist import NodeList
2036
+ >>> nodelist = NodeList()
2037
+ >>> nodelist.update_nodes()
2038
+ >>> stm = Hive(node=nodelist.get_hive_nodes())
2039
+ >>> account = Account("nectar.app", blockchain_instance=stm)
2040
+ >>> account.get_expiring_vesting_delegations()
2041
+ []
2042
+
2043
+ """
2044
+ if account is None:
2045
+ account = self["name"]
2046
+ account = extract_account_name(account)
2047
+ if not self.blockchain.is_connected():
2048
+ raise OfflineHasNoRPCException("No RPC available in offline mode!")
2049
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
2050
+ if after is None:
2051
+ after = addTzInfo(datetime.now(timezone.utc)) - timedelta(days=8)
2052
+ if self.blockchain.rpc.get_use_appbase():
2053
+ return self.blockchain.rpc.find_vesting_delegation_expirations(
2054
+ {"account": account}, api="database"
2055
+ )["delegations"]
2056
+ else:
2057
+ return self.blockchain.rpc.get_expiring_vesting_delegations(
2058
+ account, formatTimeString(after), limit
2059
+ )
2060
+
2061
+ def get_account_votes(
2062
+ self, account=None, start_author="", start_permlink="", limit=1000, start_date=None
2063
+ ):
2064
+ """Returns all votes that the account has done
2065
+
2066
+ :rtype: list
2067
+
2068
+ .. code-block:: python
2069
+
2070
+ >>> from nectar.account import Account
2071
+ >>> from nectar import Hive
2072
+ >>> from nectar.nodelist import NodeList
2073
+ >>> nodelist = NodeList()
2074
+ >>> nodelist.update_nodes()
2075
+ >>> stm = Hive(node=nodelist.get_hive_nodes())
2076
+ >>> account = Account("nectar.app", blockchain_instance=stm)
2077
+ >>> account.get_account_votes() # doctest: +SKIP
2078
+
2079
+ """
2080
+ if account is None:
2081
+ account = self["name"]
2082
+ account = extract_account_name(account)
2083
+ if not self.blockchain.is_connected():
2084
+ raise OfflineHasNoRPCException("No RPC available in offline mode!")
2085
+ # self.blockchain.rpc.set_next_node_on_empty_reply(False)
2086
+ # if self.blockchain.rpc.get_use_appbase():
2087
+ # vote_list = self.blockchain.rpc.get_account_votes(account, api="condenser")
2088
+ # else:
2089
+ # vote_list = self.blockchain.rpc.get_account_votes(account)
2090
+ # if isinstance(vote_list, dict) and "error" in vote_list:
2091
+ self.blockchain.rpc.set_next_node_on_empty_reply(True)
2092
+ vote_list = []
2093
+ finished = False
2094
+ while not finished:
2095
+ try:
2096
+ ret = self.blockchain.rpc.list_votes(
2097
+ {
2098
+ "start": [account, start_author, start_permlink],
2099
+ "limit": limit,
2100
+ "order": "by_voter_comment",
2101
+ },
2102
+ api="database",
2103
+ )["votes"]
2104
+ except SupportedByHivemind:
2105
+ return vote_list
2106
+ if len(ret) < limit:
2107
+ finished = True
2108
+ if start_author != "":
2109
+ if len(ret) == 0:
2110
+ finished = True
2111
+ ret = ret[1:]
2112
+ for vote in ret:
2113
+ if vote["voter"] != account:
2114
+ finished = True
2115
+ continue
2116
+ last_update = formatTimeString(vote["last_update"])
2117
+ if start_date is not None and last_update < start_date:
2118
+ finished = True
2119
+ continue
2120
+ vote_list.append(vote)
2121
+ start_author = vote["author"]
2122
+ start_permlink = vote["permlink"]
2123
+ return vote_list
2124
+ # else:
2125
+ # return vote_list
2126
+
2127
+ def get_vote(self, comment):
2128
+ """Returns a vote if the account has already voted for comment.
2129
+
2130
+ :param comment: can be a Comment object or a authorpermlink
2131
+ :type comment: str, Comment
2132
+ """
2133
+ from nectar.comment import Comment
2134
+
2135
+ c = Comment(comment, blockchain_instance=self.blockchain)
2136
+ for v in c["active_votes"]:
2137
+ if v["voter"] == self["name"]:
2138
+ return v
2139
+ return None
2140
+
2141
+ def has_voted(self, comment):
2142
+ """Returns if the account has already voted for comment
2143
+
2144
+ :param comment: can be a Comment object or a authorpermlink
2145
+ :type comment: str, Comment
2146
+ """
2147
+ from nectar.comment import Comment
2148
+
2149
+ c = Comment(comment, blockchain_instance=self.blockchain)
2150
+ active_votes = {v["voter"]: v for v in c["active_votes"]}
2151
+ return self["name"] in active_votes
2152
+
2153
+ def virtual_op_count(self, until=None):
2154
+ """Returns the number of individual account transactions
2155
+
2156
+ :rtype: list
2157
+ """
2158
+ if until is not None:
2159
+ return self.estimate_virtual_op_num(until, stop_diff=1)
2160
+ else:
2161
+ try:
2162
+ op_count = 0
2163
+ op_count = self._get_account_history(start=-1, limit=1)
2164
+ if op_count is None or len(op_count) == 0:
2165
+ op_count = self._get_account_history(start=-1, limit=1)
2166
+ if isinstance(op_count, list) and len(op_count) > 0 and len(op_count[0]) > 0:
2167
+ if self.blockchain.rpc.url == "https://api.hive.blog":
2168
+ return op_count[-1][0] + 1
2169
+ return op_count[-1][0]
2170
+ else:
2171
+ return 0
2172
+ except IndexError:
2173
+ return 0
2174
+
2175
+ def _get_account_history(
2176
+ self, account=None, start=-1, limit=1, operation_filter_low=None, operation_filter_high=None
2177
+ ):
2178
+ if account is None:
2179
+ account = self["name"]
2180
+ account = extract_account_name(account)
2181
+ if limit < 1:
2182
+ limit = 1
2183
+ elif limit > 1000:
2184
+ limit = 1000
2185
+ if not self.blockchain.is_connected():
2186
+ raise OfflineHasNoRPCException("No RPC available in offline mode!")
2187
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
2188
+ if operation_filter_low is None and operation_filter_high is None:
2189
+ if self.blockchain.rpc.get_use_appbase():
2190
+ try:
2191
+ ret = self.blockchain.rpc.get_account_history(
2192
+ {"account": account, "start": start, "limit": limit}, api="account_history"
2193
+ )
2194
+ if ret is not None:
2195
+ ret = ret["history"]
2196
+ except ApiNotSupported:
2197
+ ret = self.blockchain.rpc.get_account_history(
2198
+ account, start, limit, api="condenser"
2199
+ )
2200
+ else:
2201
+ ret = self.blockchain.rpc.get_account_history(account, start, limit, api="database")
2202
+ else:
2203
+ if self.blockchain.rpc.get_use_appbase():
2204
+ try:
2205
+ ret = self.blockchain.rpc.get_account_history(
2206
+ {
2207
+ "account": account,
2208
+ "start": start,
2209
+ "limit": limit,
2210
+ "operation_filter_low": operation_filter_low,
2211
+ "operation_filter_high": operation_filter_high,
2212
+ },
2213
+ api="account_history",
2214
+ )
2215
+ if ret is not None:
2216
+ ret = ret["history"]
2217
+ except ApiNotSupported:
2218
+ ret = self.blockchain.rpc.get_account_history(
2219
+ account,
2220
+ start,
2221
+ limit,
2222
+ operation_filter_low,
2223
+ operation_filter_high,
2224
+ api="condenser",
2225
+ )
2226
+ else:
2227
+ ret = self.blockchain.rpc.get_account_history(
2228
+ account,
2229
+ start,
2230
+ limit,
2231
+ operation_filter_low,
2232
+ operation_filter_high,
2233
+ api="database",
2234
+ )
2235
+ return ret
2236
+
2237
+ def _get_blocknum_from_hist(self, index, min_index=1):
2238
+ if index >= 0 and index < min_index:
2239
+ index = min_index
2240
+ op = self._get_account_history(start=(index))
2241
+ if len(op) == 0:
2242
+ return None
2243
+ return op[0][1]["block"]
2244
+
2245
+ def _get_first_blocknum(self):
2246
+ min_index = 0
2247
+ try:
2248
+ created = self._get_blocknum_from_hist(0, min_index=min_index)
2249
+ except:
2250
+ min_index = 1
2251
+ created = self._get_blocknum_from_hist(0, min_index=min_index)
2252
+ return created, min_index
2253
+
2254
+ def estimate_virtual_op_num(self, blocktime, stop_diff=0, max_count=100, min_index=None):
2255
+ """Returns an estimation of an virtual operation index for a given time or blockindex
2256
+
2257
+ :param blocktime: start time or start block index from which account
2258
+ operation should be fetched
2259
+ :type blocktime: int, datetime
2260
+ :param int stop_diff: Sets the difference between last estimation and
2261
+ new estimation at which the estimation stops. Must not be zero. (default is 1)
2262
+ :param int max_count: sets the maximum number of iterations. -1 disables this (default 100)
2263
+
2264
+ .. testsetup::
2265
+
2266
+ from nectar.account import Account
2267
+ from nectar.blockchain import Blockchain
2268
+ from datetime import datetime, timedelta
2269
+ from timeit import time as t
2270
+ .. testcode::
2271
+
2272
+ start_time = datetime.now() - timedelta(days=7)
2273
+ acc = Account("gtg")
2274
+ start_op = acc.estimate_virtual_op_num(start_time)
2275
+
2276
+ b = Blockchain()
2277
+ start_block_num = b.get_estimated_block_num(start_time)
2278
+ start_op2 = acc.estimate_virtual_op_num(start_block_num)
2279
+
2280
+ .. testcode::
2281
+
2282
+ acc = Account("gtg")
2283
+ block_num = 21248120
2284
+ start = t.time()
2285
+ op_num = acc.estimate_virtual_op_num(block_num, stop_diff=1, max_count=10)
2286
+ stop = t.time()
2287
+ print(stop - start)
2288
+ for h in acc.get_account_history(op_num, 0):
2289
+ block_est = h["block"]
2290
+ print(block_est - block_num)
2291
+
2292
+ """
2293
+ max_index = self.virtual_op_count()
2294
+ if max_index < stop_diff:
2295
+ return 0
2296
+
2297
+ # calculate everything with block numbers
2298
+ if min_index is None:
2299
+ created, min_index = self._get_first_blocknum()
2300
+ else:
2301
+ created = self._get_blocknum_from_hist(0, min_index=min_index)
2302
+
2303
+ # convert blocktime to block number if given as a datetime/date/time
2304
+ if isinstance(blocktime, (datetime, date, time)):
2305
+ b = Blockchain(blockchain_instance=self.blockchain)
2306
+ target_blocknum = b.get_estimated_block_num(addTzInfo(blocktime), accurate=True)
2307
+ else:
2308
+ target_blocknum = blocktime
2309
+
2310
+ # the requested blocknum/timestamp is before the account creation date
2311
+ if target_blocknum <= created:
2312
+ return 0
2313
+
2314
+ # get the block number from the account's latest operation
2315
+ latest_blocknum = self._get_blocknum_from_hist(-1, min_index=min_index)
2316
+
2317
+ # requested blocknum/timestamp is after the latest account operation
2318
+ if target_blocknum >= latest_blocknum:
2319
+ return max_index
2320
+
2321
+ # all account ops in a single block
2322
+ if latest_blocknum - created == 0:
2323
+ return 0
2324
+
2325
+ # set initial search range
2326
+ op_num = 0
2327
+ op_lower = 0
2328
+ block_lower = created
2329
+ op_upper = max_index
2330
+ block_upper = latest_blocknum
2331
+ last_op_num = None
2332
+ cnt = 0
2333
+
2334
+ while True:
2335
+ # check if the maximum number of iterations was reached
2336
+ if max_count != -1 and cnt >= max_count:
2337
+ # did not converge, return the current state
2338
+ return op_num
2339
+
2340
+ # linear approximation between the known upper and
2341
+ # lower bounds for the first iteration
2342
+ if cnt < 1:
2343
+ op_num = int(
2344
+ (target_blocknum - block_lower)
2345
+ / (block_upper - block_lower)
2346
+ * (op_upper - op_lower)
2347
+ + op_lower
2348
+ )
2349
+ else:
2350
+ # divide and conquer for the following iterations
2351
+ op_num = int((op_upper + op_lower) / 2)
2352
+ if op_upper == op_lower + 1: # round up if we're close to target
2353
+ op_num += 1
2354
+
2355
+ # get block number for current op number estimation
2356
+ if op_num != last_op_num:
2357
+ block_num = self._get_blocknum_from_hist(op_num, min_index=min_index)
2358
+ while block_num is None and op_num < max_index:
2359
+ op_num += 1
2360
+ block_num = self._get_blocknum_from_hist(op_num, min_index=min_index)
2361
+ last_op_num = op_num
2362
+
2363
+ # check if the required accuracy was reached
2364
+ if op_upper - op_lower <= stop_diff or op_upper == op_lower + 1:
2365
+ return op_num
2366
+
2367
+ # set new upper/lower boundaries for next iteration
2368
+ if block_num < target_blocknum:
2369
+ # current op number was too low -> search upwards
2370
+ op_lower = op_num
2371
+ block_lower = block_num
2372
+ else:
2373
+ # current op number was too high or matched the target block
2374
+ # -> search downwards
2375
+ op_upper = op_num
2376
+ block_upper = block_num
2377
+ cnt += 1
2378
+
2379
+ def get_curation_reward(self, days=7):
2380
+ """Returns the curation reward of the last `days` days
2381
+
2382
+ :param int days: limit number of days to be included int the return value
2383
+ """
2384
+ stop = addTzInfo(datetime.now(timezone.utc)) - timedelta(days=days)
2385
+ reward_vests = Amount(
2386
+ 0, self.blockchain.vest_token_symbol, blockchain_instance=self.blockchain
2387
+ )
2388
+ for reward in self.history_reverse(
2389
+ stop=stop, use_block_num=False, only_ops=["curation_reward"]
2390
+ ):
2391
+ reward_vests += Amount(reward["reward"], blockchain_instance=self.blockchain)
2392
+ return self.blockchain.vests_to_token_power(float(reward_vests))
2393
+
2394
+ def curation_stats(self):
2395
+ """Returns the curation reward of the last 24h and 7d and the average
2396
+ of the last 7 days
2397
+
2398
+ :returns: Account curation
2399
+ :rtype: dictionary
2400
+
2401
+ Sample output:
2402
+
2403
+ .. code-block:: js
2404
+
2405
+ {
2406
+ '24hr': 0.0,
2407
+ '7d': 0.0,
2408
+ 'avg': 0.0
2409
+ }
2410
+
2411
+ """
2412
+ return {
2413
+ "24hr": self.get_curation_reward(days=1),
2414
+ "7d": self.get_curation_reward(days=7),
2415
+ "avg": self.get_curation_reward(days=7) / 7,
2416
+ }
2417
+
2418
+ def _get_operation_filter(self, only_ops=[], exclude_ops=[]):
2419
+ from nectarbase.operationids import operations
2420
+
2421
+ operation_filter_low = 0
2422
+ operation_filter_high = 0
2423
+ if len(only_ops) == 0 and len(exclude_ops) == 0:
2424
+ return None, None
2425
+ if len(only_ops) > 0:
2426
+ for op in only_ops:
2427
+ op_id = operations[op]
2428
+ if op_id <= 64:
2429
+ operation_filter_low += 2**op_id
2430
+ else:
2431
+ operation_filter_high += 2 ** (op_id - 64 - 1)
2432
+ else:
2433
+ for op in operations:
2434
+ op_id = operations[op]
2435
+ if op_id <= 64:
2436
+ operation_filter_low += 2**op_id
2437
+ else:
2438
+ operation_filter_high += 2 ** (op_id - 64 - 1)
2439
+ for op in exclude_ops:
2440
+ op_id = operations[op]
2441
+ if op_id <= 64:
2442
+ operation_filter_low -= 2**op_id
2443
+ else:
2444
+ operation_filter_high -= 2 ** (op_id - 64 - 1)
2445
+ return operation_filter_low, operation_filter_high
2446
+
2447
+ def get_account_history(
2448
+ self,
2449
+ index,
2450
+ limit,
2451
+ order=-1,
2452
+ start=None,
2453
+ stop=None,
2454
+ use_block_num=True,
2455
+ only_ops=[],
2456
+ exclude_ops=[],
2457
+ raw_output=False,
2458
+ ):
2459
+ """Returns a generator for individual account transactions. This call can be used in a
2460
+ ``for`` loop.
2461
+
2462
+ :param int index: first number of transactions to return
2463
+ :param int limit: limit number of transactions to return
2464
+ :param start: start number/date of transactions to
2465
+ return (*optional*)
2466
+ :type start: int, datetime
2467
+ :param stop: stop number/date of transactions to
2468
+ return (*optional*)
2469
+ :type stop: int, datetime
2470
+ :param bool use_block_num: if true, start and stop are block numbers, otherwise virtual OP count numbers.
2471
+ :param array only_ops: Limit generator by these
2472
+ operations (*optional*)
2473
+ :param array exclude_ops: Exclude thse operations from
2474
+ generator (*optional*)
2475
+ :param int batch_size: internal api call batch size (*optional*)
2476
+ :param int order: 1 for chronological, -1 for reverse order
2477
+ :param bool raw_output: if False, the output is a dict, which
2478
+ includes all values. Otherwise, the output is list.
2479
+
2480
+ .. note::
2481
+
2482
+ only_ops and exclude_ops takes an array of strings:
2483
+ The full list of operation ID's can be found in
2484
+ nectarbase.operationids.ops.
2485
+ Example: ['transfer', 'vote']
2486
+
2487
+ """
2488
+ if order != -1 and order != 1:
2489
+ raise ValueError("order must be -1 or 1!")
2490
+ # self.blockchain.rpc.set_next_node_on_empty_reply(True)
2491
+ operation_filter_low = None
2492
+ operation_filter_high = None
2493
+ if self.blockchain.rpc.url == "https://api.hive.blog":
2494
+ operation_filter_low, operation_filter_high = self._get_operation_filter(
2495
+ only_ops=only_ops, exclude_ops=exclude_ops
2496
+ )
2497
+ try:
2498
+ txs = self._get_account_history(
2499
+ start=index,
2500
+ limit=limit,
2501
+ operation_filter_low=operation_filter_low,
2502
+ operation_filter_high=operation_filter_high,
2503
+ )
2504
+ except FilteredItemNotFound:
2505
+ txs = []
2506
+ if txs is None:
2507
+ return
2508
+ start = addTzInfo(start)
2509
+ stop = addTzInfo(stop)
2510
+
2511
+ if order == -1:
2512
+ txs_list = reversed(txs)
2513
+ else:
2514
+ txs_list = txs
2515
+ for item in txs_list:
2516
+ item_index, event = item
2517
+ if start and isinstance(start, (datetime, date, time)):
2518
+ timediff = start - formatTimeString(event["timestamp"])
2519
+ if timediff.total_seconds() * float(order) > 0:
2520
+ continue
2521
+ elif start is not None and use_block_num and order == 1 and event["block"] < start:
2522
+ continue
2523
+ elif start is not None and use_block_num and order == -1 and event["block"] > start:
2524
+ continue
2525
+ elif start is not None and not use_block_num and order == 1 and item_index < start:
2526
+ continue
2527
+ elif start is not None and not use_block_num and order == -1 and item_index > start:
2528
+ continue
2529
+ if stop is not None and isinstance(stop, (datetime, date, time)):
2530
+ timediff = stop - formatTimeString(event["timestamp"])
2531
+ if timediff.total_seconds() * float(order) < 0:
2532
+ return
2533
+ elif stop is not None and use_block_num and order == 1 and event["block"] > stop:
2534
+ return
2535
+ elif stop is not None and use_block_num and order == -1 and event["block"] < stop:
2536
+ return
2537
+ elif stop is not None and not use_block_num and order == 1 and item_index > stop:
2538
+ return
2539
+ elif stop is not None and not use_block_num and order == -1 and item_index < stop:
2540
+ return
2541
+
2542
+ if isinstance(event["op"], list):
2543
+ op_type, op = event["op"]
2544
+ else:
2545
+ op_type = event["op"]["type"]
2546
+ if len(op_type) > 10 and op_type[len(op_type) - 10 :] == "_operation":
2547
+ op_type = op_type[:-10]
2548
+ op = event["op"]["value"]
2549
+ block_props = remove_from_dict(event, keys=["op"], keep_keys=False)
2550
+
2551
+ def construct_op(account_name):
2552
+ # verbatim output from steemd
2553
+ if raw_output:
2554
+ return item
2555
+
2556
+ # index can change during reindexing in
2557
+ # future hard-forks. Thus we cannot take it for granted.
2558
+ immutable = op.copy()
2559
+ immutable.update(block_props)
2560
+ immutable.update(
2561
+ {
2562
+ "account": account_name,
2563
+ "type": op_type,
2564
+ }
2565
+ )
2566
+ _id = Blockchain.hash_op(immutable)
2567
+ immutable.update(
2568
+ {
2569
+ "_id": _id,
2570
+ "index": item_index,
2571
+ }
2572
+ )
2573
+ return immutable
2574
+
2575
+ if exclude_ops and op_type in exclude_ops:
2576
+ continue
2577
+ if not only_ops or op_type in only_ops:
2578
+ yield construct_op(self["name"])
2579
+
2580
+ def history(
2581
+ self,
2582
+ start=None,
2583
+ stop=None,
2584
+ use_block_num=True,
2585
+ only_ops=[],
2586
+ exclude_ops=[],
2587
+ batch_size=1000,
2588
+ raw_output=False,
2589
+ ):
2590
+ """Returns a generator for individual account transactions. The
2591
+ earlist operation will be first. This call can be used in a
2592
+ ``for`` loop.
2593
+
2594
+ :param start: start number/date of transactions to return (*optional*)
2595
+ :type start: int, datetime
2596
+ :param stop: stop number/date of transactions to return (*optional*)
2597
+ :type stop: int, datetime
2598
+ :param bool use_block_num: if true, start and stop are block numbers,
2599
+ otherwise virtual OP count numbers.
2600
+ :param array only_ops: Limit generator by these
2601
+ operations (*optional*)
2602
+ :param array exclude_ops: Exclude thse operations from
2603
+ generator (*optional*)
2604
+ :param int batch_size: internal api call batch size (*optional*)
2605
+ :param bool raw_output: if False, the output is a dict, which
2606
+ includes all values. Otherwise, the output is list.
2607
+
2608
+ .. note::
2609
+ only_ops and exclude_ops takes an array of strings:
2610
+ The full list of operation ID's can be found in
2611
+ nectarbase.operationids.ops.
2612
+ Example: ['transfer', 'vote']
2613
+
2614
+ .. testsetup::
2615
+
2616
+ from nectar.account import Account
2617
+ from datetime import datetime
2618
+
2619
+ .. testcode::
2620
+
2621
+ acc = Account("gtg")
2622
+ max_op_count = acc.virtual_op_count()
2623
+ # Returns the 100 latest operations
2624
+ acc_op = []
2625
+ for h in acc.history(start=max_op_count - 99, stop=max_op_count, use_block_num=False):
2626
+ acc_op.append(h)
2627
+ len(acc_op)
2628
+
2629
+ .. testoutput::
2630
+
2631
+ 100
2632
+
2633
+ .. testcode::
2634
+
2635
+ acc = Account("test")
2636
+ max_block = 21990141
2637
+ # Returns the account operation inside the last 100 block. This can be empty.
2638
+ acc_op = []
2639
+ for h in acc.history(start=max_block - 99, stop=max_block, use_block_num=True):
2640
+ acc_op.append(h)
2641
+ len(acc_op)
2642
+
2643
+ .. testoutput::
2644
+
2645
+ 0
2646
+
2647
+ .. testcode::
2648
+
2649
+ acc = Account("test")
2650
+ start_time = datetime(2018, 3, 1, 0, 0, 0)
2651
+ stop_time = datetime(2018, 3, 2, 0, 0, 0)
2652
+ # Returns the account operation from 1.4.2018 back to 1.3.2018
2653
+ acc_op = []
2654
+ for h in acc.history(start=start_time, stop=stop_time):
2655
+ acc_op.append(h)
2656
+ len(acc_op)
2657
+
2658
+ .. testoutput::
2659
+
2660
+ 0
2661
+
2662
+ """
2663
+ _limit = batch_size
2664
+ max_index = self.virtual_op_count()
2665
+ if not max_index:
2666
+ return
2667
+ start = addTzInfo(start)
2668
+ stop = addTzInfo(stop)
2669
+ if (
2670
+ start is not None
2671
+ and not use_block_num
2672
+ and not isinstance(start, (datetime, date, time))
2673
+ ):
2674
+ start_index = start
2675
+ elif start is not None and max_index > batch_size:
2676
+ created, min_index = self._get_first_blocknum()
2677
+ op_est = self.estimate_virtual_op_num(start, stop_diff=1, min_index=min_index)
2678
+ if op_est < min_index:
2679
+ op_est = min_index
2680
+ est_diff = 0
2681
+ if isinstance(start, (datetime, date, time)):
2682
+ for h in self.get_account_history(op_est, 0):
2683
+ block_date = formatTimeString(h["timestamp"])
2684
+ while op_est > est_diff + batch_size and block_date > start:
2685
+ est_diff += batch_size
2686
+ if op_est - est_diff < 0:
2687
+ est_diff = op_est
2688
+ for h in self.get_account_history(op_est - est_diff, 0):
2689
+ block_date = formatTimeString(h["timestamp"])
2690
+ elif not isinstance(start, (datetime, date, time)):
2691
+ for h in self.get_account_history(op_est, 0):
2692
+ block_num = h["block"]
2693
+ while op_est > est_diff + batch_size and block_num > start:
2694
+ est_diff += batch_size
2695
+ if op_est - est_diff < 0:
2696
+ est_diff = op_est
2697
+ for h in self.get_account_history(op_est - est_diff, 0):
2698
+ block_num = h["block"]
2699
+ start_index = op_est - est_diff
2700
+ else:
2701
+ start_index = 0
2702
+
2703
+ if stop is not None and not use_block_num and not isinstance(stop, (datetime, date, time)):
2704
+ if start_index + stop < _limit:
2705
+ _limit = stop
2706
+
2707
+ first = start_index + _limit - 1
2708
+ if first > max_index:
2709
+ _limit = max_index - start_index
2710
+ first = start_index + _limit - 1
2711
+ elif first < _limit and self.blockchain.rpc.url == "https://api.hive.blog":
2712
+ first = _limit - 1
2713
+ elif first < _limit and self.blockchain.rpc.url != "https://api.hive.blog":
2714
+ first = _limit
2715
+ last_round = False
2716
+
2717
+ if _limit < 0:
2718
+ return
2719
+ last_item_index = -1
2720
+
2721
+ if self.blockchain.rpc.url == "https://api.hive.blog" and (
2722
+ len(only_ops) > 0 or len(exclude_ops) > 0
2723
+ ):
2724
+ operation_filter = True
2725
+ else:
2726
+ operation_filter = False
2727
+
2728
+ while True:
2729
+ # RPC call
2730
+ if first < _limit - 1 and self.blockchain.rpc.url == "https://api.hive.blog":
2731
+ first = _limit - 1
2732
+ elif first < _limit and self.blockchain.rpc.url != "https://api.hive.blog":
2733
+ first = _limit
2734
+ batch_count = 0
2735
+ for item in self.get_account_history(
2736
+ first,
2737
+ _limit,
2738
+ start=None,
2739
+ stop=None,
2740
+ order=1,
2741
+ only_ops=only_ops,
2742
+ exclude_ops=exclude_ops,
2743
+ raw_output=raw_output,
2744
+ ):
2745
+ batch_count += 1
2746
+ if raw_output:
2747
+ item_index, event = item
2748
+ op_type, op = event["op"]
2749
+ timestamp = event["timestamp"]
2750
+ block_num = event["block"]
2751
+ else:
2752
+ item_index = item["index"]
2753
+ op_type = item["type"]
2754
+ timestamp = item["timestamp"]
2755
+ block_num = item["block"]
2756
+ if start is not None and isinstance(start, (datetime, date, time)):
2757
+ timediff = start - formatTimeString(timestamp)
2758
+ if timediff.total_seconds() > 0:
2759
+ continue
2760
+ elif start is not None and use_block_num and block_num < start:
2761
+ continue
2762
+ elif start is not None and not use_block_num and item_index < start:
2763
+ continue
2764
+ elif last_item_index >= item_index:
2765
+ continue
2766
+ if stop is not None and isinstance(stop, (datetime, date, time)):
2767
+ timediff = stop - formatTimeString(timestamp)
2768
+ if timediff.total_seconds() < 0:
2769
+ first = max_index + _limit
2770
+ return
2771
+ elif stop is not None and use_block_num and block_num > stop:
2772
+ return
2773
+ elif stop is not None and not use_block_num and item_index > stop:
2774
+ return
2775
+ if operation_filter:
2776
+ yield item
2777
+ else:
2778
+ if exclude_ops and op_type in exclude_ops:
2779
+ continue
2780
+ if not only_ops or op_type in only_ops:
2781
+ yield item
2782
+ last_item_index = item_index
2783
+ if first < max_index and first + _limit >= max_index and not last_round:
2784
+ _limit = max_index - first
2785
+ first = max_index
2786
+ last_round = True
2787
+ else:
2788
+ if (
2789
+ operation_filter
2790
+ and batch_count < _limit
2791
+ and first + 2000 < max_index
2792
+ and _limit == 1000
2793
+ ):
2794
+ first += 2000
2795
+ else:
2796
+ first += _limit
2797
+ if (
2798
+ stop is not None
2799
+ and not use_block_num
2800
+ and isinstance(stop, int)
2801
+ and first >= stop + _limit + 1
2802
+ ):
2803
+ break
2804
+ elif first > max_index or last_round:
2805
+ break
2806
+
2807
+ def history_reverse(
2808
+ self,
2809
+ start=None,
2810
+ stop=None,
2811
+ use_block_num=True,
2812
+ only_ops=[],
2813
+ exclude_ops=[],
2814
+ batch_size=1000,
2815
+ raw_output=False,
2816
+ ):
2817
+ """Returns a generator for individual account transactions. The
2818
+ latest operation will be first. This call can be used in a
2819
+ ``for`` loop.
2820
+
2821
+ :param start: start number/date of transactions to
2822
+ return. If negative the virtual_op_count is added. (*optional*)
2823
+ :type start: int, datetime
2824
+ :param stop: stop number/date of transactions to
2825
+ return. If negative the virtual_op_count is added. (*optional*)
2826
+ :type stop: int, datetime
2827
+ :param bool use_block_num: if true, start and stop are block numbers,
2828
+ otherwise virtual OP count numbers.
2829
+ :param array only_ops: Limit generator by these
2830
+ operations (*optional*)
2831
+ :param array exclude_ops: Exclude thse operations from
2832
+ generator (*optional*)
2833
+ :param int batch_size: internal api call batch size (*optional*)
2834
+ :param bool raw_output: if False, the output is a dict, which
2835
+ includes all values. Otherwise, the output is list.
2836
+
2837
+ .. note::
2838
+ only_ops and exclude_ops takes an array of strings:
2839
+ The full list of operation ID's can be found in
2840
+ nectarbase.operationids.ops.
2841
+ Example: ['transfer', 'vote']
2842
+
2843
+ .. testsetup::
2844
+
2845
+ from nectar.account import Account
2846
+ from datetime import datetime
2847
+
2848
+ .. testcode::
2849
+
2850
+ acc = Account("gtg")
2851
+ max_op_count = acc.virtual_op_count()
2852
+ # Returns the 100 latest operations
2853
+ acc_op = []
2854
+ for h in acc.history_reverse(start=max_op_count, stop=max_op_count - 99, use_block_num=False):
2855
+ acc_op.append(h)
2856
+ len(acc_op)
2857
+
2858
+ .. testoutput::
2859
+
2860
+ 100
2861
+
2862
+ .. testcode::
2863
+
2864
+ max_block = 21990141
2865
+ acc = Account("test")
2866
+ # Returns the account operation inside the last 100 block. This can be empty.
2867
+ acc_op = []
2868
+ for h in acc.history_reverse(start=max_block, stop=max_block-100, use_block_num=True):
2869
+ acc_op.append(h)
2870
+ len(acc_op)
2871
+
2872
+ .. testoutput::
2873
+
2874
+ 0
2875
+
2876
+ .. testcode::
2877
+
2878
+ acc = Account("test")
2879
+ start_time = datetime(2018, 4, 1, 0, 0, 0)
2880
+ stop_time = datetime(2018, 3, 1, 0, 0, 0)
2881
+ # Returns the account operation from 1.4.2018 back to 1.3.2018
2882
+ acc_op = []
2883
+ for h in acc.history_reverse(start=start_time, stop=stop_time):
2884
+ acc_op.append(h)
2885
+ len(acc_op)
2886
+
2887
+ .. testoutput::
2888
+
2889
+ 0
2890
+
2891
+ """
2892
+ _limit = batch_size
2893
+ first = self.virtual_op_count()
2894
+ start = addTzInfo(start)
2895
+ stop = addTzInfo(stop)
2896
+ if not first or not batch_size:
2897
+ return
2898
+ if start is not None and isinstance(start, int) and start < 0 and not use_block_num:
2899
+ start += first
2900
+ elif start is not None and isinstance(start, int) and not use_block_num:
2901
+ first = start
2902
+ elif start is not None and first > batch_size:
2903
+ created, min_index = self._get_first_blocknum()
2904
+ op_est = self.estimate_virtual_op_num(start, stop_diff=1, min_index=min_index)
2905
+ est_diff = 0
2906
+ if op_est < min_index:
2907
+ op_est = min_index
2908
+ if isinstance(start, (datetime, date, time)):
2909
+ for h in self.get_account_history(op_est, 0):
2910
+ block_date = formatTimeString(h["timestamp"])
2911
+ while op_est + est_diff + batch_size < first and block_date < start:
2912
+ est_diff += batch_size
2913
+ if op_est + est_diff > first:
2914
+ est_diff = first - op_est
2915
+ for h in self.get_account_history(op_est + est_diff, 0):
2916
+ block_date = formatTimeString(h["timestamp"])
2917
+ else:
2918
+ for h in self.get_account_history(op_est, 0):
2919
+ block_num = h["block"]
2920
+ while op_est + est_diff + batch_size < first and block_num < start:
2921
+ est_diff += batch_size
2922
+ if op_est + est_diff > first:
2923
+ est_diff = first - op_est
2924
+ for h in self.get_account_history(op_est + est_diff, 0):
2925
+ block_num = h["block"]
2926
+ first = op_est + est_diff
2927
+ if stop is not None and isinstance(stop, int) and stop < 0 and not use_block_num:
2928
+ stop += first
2929
+
2930
+ if self.blockchain.rpc.url == "https://api.hive.blog" and (
2931
+ len(only_ops) > 0 or len(exclude_ops) > 0
2932
+ ):
2933
+ operation_filter = True
2934
+ else:
2935
+ operation_filter = False
2936
+
2937
+ last_item_index = first + 1
2938
+ while True:
2939
+ # RPC call
2940
+ if first - _limit < 0 and self.blockchain.rpc.url == "https://api.hive.blog":
2941
+ _limit = first + 1
2942
+ elif first - _limit < 0 and self.blockchain.rpc.url != "https://api.hive.blog":
2943
+ _limit = first
2944
+ batch_count = 0
2945
+ for item in self.get_account_history(
2946
+ first,
2947
+ _limit,
2948
+ start=None,
2949
+ stop=None,
2950
+ order=-1,
2951
+ only_ops=only_ops,
2952
+ exclude_ops=exclude_ops,
2953
+ raw_output=raw_output,
2954
+ ):
2955
+ batch_count += 1
2956
+ if raw_output:
2957
+ item_index, event = item
2958
+ op_type, op = event["op"]
2959
+ timestamp = event["timestamp"]
2960
+ block_num = event["block"]
2961
+ else:
2962
+ item_index = item["index"]
2963
+ op_type = item["type"]
2964
+ timestamp = item["timestamp"]
2965
+ block_num = item["block"]
2966
+ if start is not None and isinstance(start, (datetime, date, time)):
2967
+ timediff = start - formatTimeString(timestamp)
2968
+ if timediff.total_seconds() < 0:
2969
+ continue
2970
+ elif start is not None and use_block_num and block_num > start:
2971
+ continue
2972
+ elif start is not None and not use_block_num and item_index > start:
2973
+ continue
2974
+ elif last_item_index <= item_index:
2975
+ continue
2976
+ if stop is not None and isinstance(stop, (datetime, date, time)):
2977
+ timediff = stop - formatTimeString(timestamp)
2978
+ if timediff.total_seconds() > 0:
2979
+ first = 0
2980
+ return
2981
+ elif stop is not None and use_block_num and block_num < stop:
2982
+ first = 0
2983
+ return
2984
+ elif stop is not None and not use_block_num and item_index < stop:
2985
+ first = 0
2986
+ return
2987
+ if operation_filter:
2988
+ yield item
2989
+ else:
2990
+ if exclude_ops and op_type in exclude_ops:
2991
+ continue
2992
+ if not only_ops or op_type in only_ops:
2993
+ yield item
2994
+ last_item_index = item_index
2995
+ if operation_filter and batch_count < _limit and _limit == 1000:
2996
+ first -= 2000
2997
+ else:
2998
+ first -= _limit
2999
+ if first < 1:
3000
+ break
3001
+
3002
+ def mute(self, mute, account=None):
3003
+ """Mute another account
3004
+
3005
+ :param str mute: Mute this account
3006
+ :param str account: (optional) the account to allow access
3007
+ to (defaults to ``default_account``)
3008
+
3009
+ """
3010
+ return self.follow(mute, what=["ignore"], account=account)
3011
+
3012
+ def unfollow(self, unfollow, account=None):
3013
+ """Unfollow/Unmute another account's blog
3014
+
3015
+ :param str unfollow: Unfollow/Unmute this account
3016
+ :param str account: (optional) the account to allow access
3017
+ to (defaults to ``default_account``)
3018
+
3019
+ """
3020
+ return self.follow(unfollow, what=[], account=account)
3021
+
3022
+ def follow(self, other, what=["blog"], account=None):
3023
+ """Follow/Unfollow/Mute/Unmute another account's blog
3024
+
3025
+ .. note:: what can be one of the following on HIVE:
3026
+ blog, ignore, blacklist, unblacklist, follow_blacklist,
3027
+ unfollow_blacklist, follow_muted, unfollow_muted
3028
+
3029
+ :param str/list other: Follow this account / accounts (only hive)
3030
+ :param list what: List of states to follow.
3031
+ ``['blog']`` means to follow ``other``,
3032
+ ``[]`` means to unfollow/unmute ``other``,
3033
+ ``['ignore']`` means to ignore ``other``,
3034
+ (defaults to ``['blog']``)
3035
+ :param str account: (optional) the account to allow access
3036
+ to (defaults to ``default_account``)
3037
+
3038
+ """
3039
+ if account is None:
3040
+ account = self["name"]
3041
+ account = extract_account_name(account)
3042
+ if not account:
3043
+ raise ValueError("You need to provide an account")
3044
+ if not other:
3045
+ raise ValueError("You need to provide an account to follow/unfollow/mute/unmute")
3046
+ if isinstance(other, str) and other.find(",") > 0:
3047
+ other = other.split(",")
3048
+ json_body = ["follow", {"follower": account, "following": other, "what": what}]
3049
+ return self.blockchain.custom_json("follow", json_body, required_posting_auths=[account])
3050
+
3051
+ def update_account_profile(self, profile, account=None, **kwargs):
3052
+ """Update an account's profile in json_metadata
3053
+
3054
+ :param dict profile: The new profile to use
3055
+ :param str account: (optional) the account to allow access
3056
+ to (defaults to ``default_account``)
3057
+
3058
+ Sample profile structure:
3059
+
3060
+ .. code-block:: js
3061
+
3062
+ {
3063
+ 'name': 'Holger',
3064
+ 'about': 'nectar Developer',
3065
+ 'location': 'Germany',
3066
+ 'profile_image': 'https://c1.staticflickr.com/5/4715/38733717165_7070227c89_n.jpg',
3067
+ 'cover_image': 'https://farm1.staticflickr.com/894/26382750057_69f5c8e568.jpg',
3068
+ 'website': 'https://github.com/holgern/nectar'
3069
+ }
3070
+
3071
+ .. code-block:: python
3072
+
3073
+ from nectar.account import Account
3074
+ account = Account("test")
3075
+ profile = account.profile
3076
+ profile["about"] = "test account"
3077
+ account.update_account_profile(profile)
3078
+
3079
+ """
3080
+ if account is None:
3081
+ account = self
3082
+ else:
3083
+ account = Account(account, blockchain_instance=self.blockchain)
3084
+
3085
+ if not isinstance(profile, dict):
3086
+ raise ValueError("Profile must be a dict type!")
3087
+
3088
+ if self["json_metadata"] == "":
3089
+ metadata = {}
3090
+ else:
3091
+ metadata = json.loads(self["json_metadata"])
3092
+ metadata["profile"] = profile
3093
+ return self.update_account_metadata(metadata)
3094
+
3095
+ def update_account_metadata(self, metadata, account=None, **kwargs):
3096
+ """Update an account's profile in json_metadata
3097
+
3098
+ :param dict metadata: The new metadata to use
3099
+ :param str account: (optional) the account to allow access
3100
+ to (defaults to ``default_account``)
3101
+
3102
+ """
3103
+ if account is None:
3104
+ account = self
3105
+ else:
3106
+ account = Account(account, blockchain_instance=self.blockchain)
3107
+ if isinstance(metadata, dict):
3108
+ metadata = json.dumps(metadata)
3109
+ elif not isinstance(metadata, str):
3110
+ raise ValueError("Profile must be a dict or string!")
3111
+ op = operations.Account_update(
3112
+ **{
3113
+ "account": account["name"],
3114
+ "memo_key": account["memo_key"],
3115
+ "json_metadata": metadata,
3116
+ "prefix": self.blockchain.prefix,
3117
+ }
3118
+ )
3119
+ return self.blockchain.finalizeOp(op, account, "active", **kwargs)
3120
+
3121
+ def update_account_jsonmetadata(self, metadata, account=None, **kwargs):
3122
+ """Update an account's profile in json_metadata using the posting key
3123
+
3124
+ :param dict metadata: The new metadata to use
3125
+ :param str account: (optional) the account to allow access
3126
+ to (defaults to ``default_account``)
3127
+
3128
+ """
3129
+ if account is None:
3130
+ account = self
3131
+ else:
3132
+ account = Account(account, blockchain_instance=self.blockchain)
3133
+ if isinstance(metadata, dict):
3134
+ metadata = json.dumps(metadata)
3135
+ elif not isinstance(metadata, str):
3136
+ raise ValueError("Profile must be a dict or string!")
3137
+ op = operations.Account_update2(
3138
+ **{
3139
+ "account": account["name"],
3140
+ "posting_json_metadata": metadata,
3141
+ "prefix": self.blockchain.prefix,
3142
+ }
3143
+ )
3144
+ return self.blockchain.finalizeOp(op, account, "posting", **kwargs)
3145
+
3146
+ # -------------------------------------------------------------------------
3147
+ # Approval and Disapproval of witnesses
3148
+ # -------------------------------------------------------------------------
3149
+ def approvewitness(self, witness, account=None, approve=True, **kwargs):
3150
+ """Approve a witness
3151
+
3152
+ :param list witness: list of Witness name or id
3153
+ :param str account: (optional) the account to allow access
3154
+ to (defaults to ``default_account``)
3155
+
3156
+ """
3157
+ if account is None:
3158
+ account = self
3159
+ else:
3160
+ account = Account(account, blockchain_instance=self.blockchain)
3161
+
3162
+ # if not isinstance(witnesses, (list, set, tuple)):
3163
+ # witnesses = {witnesses}
3164
+
3165
+ # for witness in witnesses:
3166
+ # witness = Witness(witness, blockchain_instance=self)
3167
+
3168
+ op = operations.Account_witness_vote(
3169
+ **{
3170
+ "account": account["name"],
3171
+ "witness": witness,
3172
+ "approve": approve,
3173
+ "prefix": self.blockchain.prefix,
3174
+ }
3175
+ )
3176
+ return self.blockchain.finalizeOp(op, account, "active", **kwargs)
3177
+
3178
+ def disapprovewitness(self, witness, account=None, **kwargs):
3179
+ """Disapprove a witness
3180
+
3181
+ :param list witness: list of Witness name or id
3182
+ :param str account: (optional) the account to allow access
3183
+ to (defaults to ``default_account``)
3184
+ """
3185
+ return self.approvewitness(witness=witness, account=account, approve=False)
3186
+
3187
+ def setproxy(self, proxy="", account=None):
3188
+ """Set the witness and proposal system proxy of an account
3189
+
3190
+ :param proxy: The account to set the proxy to (Leave empty for removing the proxy)
3191
+ :type proxy: str or Account
3192
+ :param account: The account the proxy should be set for
3193
+ :type account: str or Account
3194
+ """
3195
+ if account is None:
3196
+ account = self
3197
+ elif isinstance(account, Account):
3198
+ pass
3199
+ else:
3200
+ account = Account(account)
3201
+
3202
+ if isinstance(proxy, str):
3203
+ proxy_name = proxy
3204
+ else:
3205
+ proxy_name = proxy["name"]
3206
+ op = operations.Account_witness_proxy(**{"account": account.name, "proxy": proxy_name})
3207
+ return self.blockchain.finalizeOp(op, account, "active")
3208
+
3209
+ def update_memo_key(self, key, account=None, **kwargs):
3210
+ """Update an account's memo public key
3211
+
3212
+ This method does **not** add any private keys to your
3213
+ wallet but merely changes the memo public key.
3214
+
3215
+ :param str key: New memo public key
3216
+ :param str account: (optional) the account to allow access
3217
+ to (defaults to ``default_account``)
3218
+ """
3219
+ if account is None:
3220
+ account = self
3221
+ else:
3222
+ account = Account(account, blockchain_instance=self.blockchain)
3223
+
3224
+ PublicKey(key, prefix=self.blockchain.prefix)
3225
+
3226
+ account["memo_key"] = key
3227
+ op = operations.Account_update(
3228
+ **{
3229
+ "account": account["name"],
3230
+ "memo_key": account["memo_key"],
3231
+ "json_metadata": account["json_metadata"],
3232
+ "prefix": self.blockchain.prefix,
3233
+ }
3234
+ )
3235
+ return self.blockchain.finalizeOp(op, account, "active", **kwargs)
3236
+
3237
+ def update_account_keys(self, new_password, account=None, **kwargs):
3238
+ """Updates all account keys
3239
+
3240
+ This method does **not** add any private keys to your
3241
+ wallet but merely changes the public keys.
3242
+
3243
+ :param str new_password: is used to derive the owner, active,
3244
+ posting and memo key
3245
+ :param str account: (optional) the account to allow access
3246
+ to (defaults to ``default_account``)
3247
+ """
3248
+ if account is None:
3249
+ account = self
3250
+ else:
3251
+ account = Account(account, blockchain_instance=self.blockchain)
3252
+
3253
+ key_auths = {}
3254
+ for role in ["owner", "active", "posting", "memo"]:
3255
+ pk = PasswordKey(account["name"], new_password, role=role)
3256
+ key_auths[role] = format(pk.get_public_key(), self.blockchain.prefix)
3257
+
3258
+ op = operations.Account_update(
3259
+ **{
3260
+ "account": account["name"],
3261
+ "owner": {
3262
+ "account_auths": [],
3263
+ "key_auths": [[key_auths["owner"], 1]],
3264
+ "address_auths": [],
3265
+ "weight_threshold": 1,
3266
+ },
3267
+ "active": {
3268
+ "account_auths": [],
3269
+ "key_auths": [[key_auths["active"], 1]],
3270
+ "address_auths": [],
3271
+ "weight_threshold": 1,
3272
+ },
3273
+ "posting": {
3274
+ "account_auths": account["posting"]["account_auths"],
3275
+ "key_auths": [[key_auths["posting"], 1]],
3276
+ "address_auths": [],
3277
+ "weight_threshold": 1,
3278
+ },
3279
+ "memo_key": key_auths["memo"],
3280
+ "json_metadata": account["json_metadata"],
3281
+ "prefix": self.blockchain.prefix,
3282
+ }
3283
+ )
3284
+
3285
+ return self.blockchain.finalizeOp(op, account, "owner", **kwargs)
3286
+
3287
+ def change_recovery_account(self, new_recovery_account, account=None, **kwargs):
3288
+ """Request a change of the recovery account.
3289
+
3290
+ .. note:: It takes 30 days until the change applies. Another
3291
+ request within this time restarts the 30 day period.
3292
+ Setting the current recovery account again cancels any
3293
+ pending change request.
3294
+
3295
+ :param str new_recovery_account: account name of the new
3296
+ recovery account
3297
+ :param str account: (optional) the account to change the
3298
+ recovery account for (defaults to ``default_account``)
3299
+
3300
+ """
3301
+ if account is None:
3302
+ account = self
3303
+ else:
3304
+ account = Account(account, blockchain_instance=self.blockchain)
3305
+ # Account() lookup to make sure the new account is valid
3306
+ new_rec_acc = Account(new_recovery_account, blockchain_instance=self.blockchain)
3307
+ op = operations.Change_recovery_account(
3308
+ **{
3309
+ "account_to_recover": account["name"],
3310
+ "new_recovery_account": new_rec_acc["name"],
3311
+ "extensions": [],
3312
+ }
3313
+ )
3314
+ return self.blockchain.finalizeOp(op, account, "owner", **kwargs)
3315
+
3316
+ # -------------------------------------------------------------------------
3317
+ # Simple Transfer
3318
+ # -------------------------------------------------------------------------
3319
+ def transfer(
3320
+ self, to, amount, asset, memo="", skip_account_check=False, account=None, **kwargs
3321
+ ):
3322
+ """Transfer an asset to another account.
3323
+
3324
+ :param str to: Recipient
3325
+ :param float amount: Amount to transfer
3326
+ :param str asset: Asset to transfer
3327
+ :param str memo: (optional) Memo, may begin with `#` for encrypted
3328
+ messaging
3329
+ :param bool skip_account_check: (optional) When True, the receiver
3330
+ account name is not checked to speed up sending multiple transfers in a row
3331
+ :param str account: (optional) the source account for the transfer
3332
+ if not ``default_account``
3333
+
3334
+
3335
+ Transfer example:
3336
+
3337
+ .. code-block:: python
3338
+
3339
+ from nectar.account import Account
3340
+ from nectar import Hive
3341
+ active_wif = "5xxxx"
3342
+ stm = Hive(keys=[active_wif])
3343
+ acc = Account("test", blockchain_instance=stm)
3344
+ acc.transfer("test1", 1, "HIVE", "test")
3345
+
3346
+ """
3347
+
3348
+ if account is None:
3349
+ account = self
3350
+ elif not skip_account_check:
3351
+ account = Account(account, blockchain_instance=self.blockchain)
3352
+ amount = Amount(amount, asset, blockchain_instance=self.blockchain)
3353
+ if not skip_account_check:
3354
+ to = Account(to, blockchain_instance=self.blockchain)
3355
+
3356
+ to_name = extract_account_name(to)
3357
+ account_name = extract_account_name(account)
3358
+ if memo and memo[0] == "#":
3359
+ from .memo import Memo
3360
+
3361
+ memoObj = Memo(from_account=account, to_account=to, blockchain_instance=self.blockchain)
3362
+ memo = memoObj.encrypt(memo[1:])["message"]
3363
+
3364
+ op = operations.Transfer(
3365
+ **{
3366
+ "amount": amount,
3367
+ "to": to_name,
3368
+ "memo": memo,
3369
+ "from": account_name,
3370
+ "prefix": self.blockchain.prefix,
3371
+ "json_str": not bool(self.blockchain.config["use_condenser"]),
3372
+ }
3373
+ )
3374
+ return self.blockchain.finalizeOp(op, account, "active", **kwargs)
3375
+
3376
+ # -------------------------------------------------------------------------------
3377
+ # Recurring Transfer added in hf25
3378
+ # -------------------------------------------------------------------------------
3379
+ def recurring_transfer(
3380
+ self,
3381
+ to,
3382
+ amount,
3383
+ asset,
3384
+ recurrence,
3385
+ executions,
3386
+ memo="",
3387
+ skip_account_check=False,
3388
+ account=None,
3389
+ **kwargs,
3390
+ ):
3391
+ """Transfer an asset to another account.
3392
+
3393
+ :param str to: Recipient
3394
+ :param float amount: Amount to transfer in each occurence, must have 3 decimal points
3395
+ :param str asset: Asset to transfer
3396
+ :param int recurrence: How often in hours to execute transfer
3397
+ :param int executions: Number of times to recur before stopping execution
3398
+ :param str memo: (optional) Memo, may begin with `#` for encrypted
3399
+ messaging
3400
+ :param bool skip_account_check: (optional) When True, the receiver
3401
+ account name is not checked to speed up sending multiple transfers in a row
3402
+ :param str account: (optional) the source account for the transfer
3403
+ if not ``default_account``
3404
+
3405
+
3406
+ Transfer example:
3407
+
3408
+ .. code-block:: python
3409
+
3410
+ from nectar.account import Account
3411
+ from nectar import Hive
3412
+ active_wif = "5xxxx"
3413
+ stm = Hive(keys=[active_wif])
3414
+ acc = Account("test", blockchain_instance=stm)
3415
+ acc.transfer("test1", 1, "HIVE", 48, 5, "test")
3416
+
3417
+ """
3418
+
3419
+ if account is None:
3420
+ account = self
3421
+ elif not skip_account_check:
3422
+ account = Account(account, blockchain_instance=self.blockchain)
3423
+ amount = Amount(amount, asset, blockchain_instance=self.blockchain)
3424
+ if not skip_account_check:
3425
+ to = Account(to, blockchain_instance=self.blockchain)
3426
+
3427
+ to_name = extract_account_name(to)
3428
+ account_name = extract_account_name(account)
3429
+ if memo and memo[0] == "#":
3430
+ from .memo import Memo
3431
+
3432
+ memoObj = Memo(from_account=account, to_account=to, blockchain_instance=self.blockchain)
3433
+ memo = memoObj.encrypt(memo[1:])["message"]
3434
+
3435
+ op = operations.Recurring_transfer(
3436
+ **{
3437
+ "amount": amount,
3438
+ "to": to_name,
3439
+ "memo": memo,
3440
+ "from": account_name,
3441
+ "recurrence": recurrence,
3442
+ "executions": executions,
3443
+ "prefix": self.blockchain.prefix,
3444
+ "json_str": not bool(self.blockchain.config["use_condenser"]),
3445
+ }
3446
+ )
3447
+ return self.blockchain.finalizeOp(op, account, "active", **kwargs)
3448
+
3449
+ def transfer_to_vesting(
3450
+ self, amount, to=None, account=None, skip_account_check=False, **kwargs
3451
+ ):
3452
+ """Vest STEEM
3453
+
3454
+ :param float amount: Amount to transfer
3455
+ :param str to: Recipient (optional) if not set equal to account
3456
+ :param str account: (optional) the source account for the transfer
3457
+ if not ``default_account``
3458
+ :param bool skip_account_check: (optional) When True, the receiver
3459
+ account name is not checked to speed up sending multiple transfers in a row
3460
+ """
3461
+ if account is None:
3462
+ account = self
3463
+ elif not skip_account_check:
3464
+ account = Account(account, blockchain_instance=self.blockchain)
3465
+ amount = self._check_amount(amount, self.blockchain.token_symbol)
3466
+ if to is None and skip_account_check:
3467
+ to = self["name"] # powerup on the same account
3468
+ elif to is None:
3469
+ to = self
3470
+ if not skip_account_check:
3471
+ to = Account(to, blockchain_instance=self.blockchain)
3472
+ to_name = extract_account_name(to)
3473
+ account_name = extract_account_name(account)
3474
+
3475
+ op = operations.Transfer_to_vesting(
3476
+ **{
3477
+ "from": account_name,
3478
+ "to": to_name,
3479
+ "amount": amount,
3480
+ "prefix": self.blockchain.prefix,
3481
+ "json_str": not bool(self.blockchain.config["use_condenser"]),
3482
+ }
3483
+ )
3484
+ return self.blockchain.finalizeOp(op, account, "active", **kwargs)
3485
+
3486
+ def convert(self, amount, account=None, request_id=None):
3487
+ """Convert SteemDollars to Steem (takes 3.5 days to settle)
3488
+
3489
+ :param float amount: amount of SBD to convert
3490
+ :param str account: (optional) the source account for the transfer
3491
+ if not ``default_account``
3492
+ :param str request_id: (optional) identifier for tracking the
3493
+ conversion`
3494
+
3495
+ """
3496
+ if account is None:
3497
+ account = self
3498
+ else:
3499
+ account = Account(account, blockchain_instance=self.blockchain)
3500
+ amount = self._check_amount(amount, self.blockchain.backed_token_symbol)
3501
+ if request_id:
3502
+ request_id = int(request_id)
3503
+ else:
3504
+ request_id = random.getrandbits(32)
3505
+ op = operations.Convert(
3506
+ **{
3507
+ "owner": account["name"],
3508
+ "requestid": request_id,
3509
+ "amount": amount,
3510
+ "prefix": self.blockchain.prefix,
3511
+ "json_str": not bool(self.blockchain.config["use_condenser"]),
3512
+ }
3513
+ )
3514
+
3515
+ return self.blockchain.finalizeOp(op, account, "active")
3516
+
3517
+ # Added to differentiate and match the addition of the HF25 convert operation
3518
+ def collateralized_convert(self, amount, account=None, request_id=None, **kwargs):
3519
+ """Convert Hive dollars to Hive (this method is meant to be more instant)
3520
+ and reflect the method added in HF25
3521
+
3522
+ :param float amount: amount of SBD to convert
3523
+ :param str account: (optional) the source account for the transfer
3524
+ if not ``default_account``
3525
+ :param str request_id: (optional) identifier for tracking the
3526
+ conversion`
3527
+
3528
+ """
3529
+ if account is None:
3530
+ account = self
3531
+ else:
3532
+ account = Account(account, blockchain_instance=self.blockchain)
3533
+ amount = self._check_amount(amount, self.blockchain.backed_token_symbol)
3534
+ if request_id:
3535
+ request_id = int(request_id)
3536
+ else:
3537
+ request_id = random.getrandbits(32)
3538
+ op = operations.Collateralized_convert(
3539
+ **{
3540
+ "owner": account["name"],
3541
+ "requestid": request_id,
3542
+ "amount": amount,
3543
+ "prefix": self.blockchain.prefix,
3544
+ "json_str": not bool(self.blockchain.config["use_condenser"]),
3545
+ }
3546
+ )
3547
+
3548
+ return self.blockchain.finalizeOp(op, account, "active", **kwargs)
3549
+
3550
+ def transfer_to_savings(self, amount, asset, memo, to=None, account=None, **kwargs):
3551
+ """Transfer SBD or STEEM into a 'savings' account.
3552
+
3553
+ :param float amount: STEEM or SBD amount
3554
+ :param float asset: 'STEEM' or 'SBD'
3555
+ :param str memo: (optional) Memo
3556
+ :param str to: (optional) the source account for the transfer if
3557
+ not ``default_account``
3558
+ :param str account: (optional) the source account for the transfer
3559
+ if not ``default_account``
3560
+
3561
+ """
3562
+ if asset not in [self.blockchain.token_symbol, self.blockchain.backed_token_symbol]:
3563
+ raise AssertionError()
3564
+
3565
+ if account is None:
3566
+ account = self
3567
+ else:
3568
+ account = Account(account, blockchain_instance=self.blockchain)
3569
+
3570
+ amount = Amount(amount, asset, blockchain_instance=self.blockchain)
3571
+ if to is None:
3572
+ to = account # move to savings on same account
3573
+ else:
3574
+ to = Account(to, blockchain_instance=self.blockchain)
3575
+ op = operations.Transfer_to_savings(
3576
+ **{
3577
+ "from": account["name"],
3578
+ "to": to["name"],
3579
+ "amount": amount,
3580
+ "memo": memo,
3581
+ "prefix": self.blockchain.prefix,
3582
+ "json_str": not bool(self.blockchain.config["use_condenser"]),
3583
+ }
3584
+ )
3585
+ return self.blockchain.finalizeOp(op, account, "active", **kwargs)
3586
+
3587
+ def transfer_from_savings(
3588
+ self, amount, asset, memo, request_id=None, to=None, account=None, **kwargs
3589
+ ):
3590
+ """Withdraw SBD or STEEM from 'savings' account.
3591
+
3592
+ :param float amount: STEEM or SBD amount
3593
+ :param float asset: 'STEEM' or 'SBD'
3594
+ :param str memo: (optional) Memo
3595
+ :param str request_id: (optional) identifier for tracking or
3596
+ cancelling the withdrawal
3597
+ :param str to: (optional) the source account for the transfer if
3598
+ not ``default_account``
3599
+ :param str account: (optional) the source account for the transfer
3600
+ if not ``default_account``
3601
+
3602
+ """
3603
+ if asset not in [self.blockchain.token_symbol, self.blockchain.backed_token_symbol]:
3604
+ raise AssertionError()
3605
+
3606
+ if account is None:
3607
+ account = self
3608
+ else:
3609
+ account = Account(account, blockchain_instance=self.blockchain)
3610
+ if to is None:
3611
+ to = account # move to savings on same account
3612
+ else:
3613
+ to = Account(to, blockchain_instance=self.blockchain)
3614
+ amount = Amount(amount, asset, blockchain_instance=self.blockchain)
3615
+ if request_id:
3616
+ request_id = int(request_id)
3617
+ else:
3618
+ request_id = random.getrandbits(32)
3619
+
3620
+ op = operations.Transfer_from_savings(
3621
+ **{
3622
+ "from": account["name"],
3623
+ "request_id": request_id,
3624
+ "to": to["name"],
3625
+ "amount": amount,
3626
+ "memo": memo,
3627
+ "prefix": self.blockchain.prefix,
3628
+ "json_str": not bool(self.blockchain.config["use_condenser"]),
3629
+ }
3630
+ )
3631
+ return self.blockchain.finalizeOp(op, account, "active", **kwargs)
3632
+
3633
+ def cancel_transfer_from_savings(self, request_id, account=None, **kwargs):
3634
+ """Cancel a withdrawal from 'savings' account.
3635
+
3636
+ :param str request_id: Identifier for tracking or cancelling
3637
+ the withdrawal
3638
+ :param str account: (optional) the source account for the transfer
3639
+ if not ``default_account``
3640
+
3641
+ """
3642
+ if account is None:
3643
+ account = self
3644
+ else:
3645
+ account = Account(account, blockchain_instance=self.blockchain)
3646
+ op = operations.Cancel_transfer_from_savings(
3647
+ **{
3648
+ "from": account["name"],
3649
+ "request_id": request_id,
3650
+ "prefix": self.blockchain.prefix,
3651
+ }
3652
+ )
3653
+ return self.blockchain.finalizeOp(op, account, "active", **kwargs)
3654
+
3655
+ def _check_amount(self, amount, symbol):
3656
+ if isinstance(amount, (float, integer_types)):
3657
+ amount = Amount(amount, symbol, blockchain_instance=self.blockchain)
3658
+ elif (
3659
+ isinstance(amount, string_types)
3660
+ and amount.replace(".", "", 1).replace(",", "", 1).isdigit()
3661
+ ):
3662
+ amount = Amount(float(amount), symbol, blockchain_instance=self.blockchain)
3663
+ else:
3664
+ amount = Amount(amount, blockchain_instance=self.blockchain)
3665
+ if not amount["symbol"] == symbol:
3666
+ raise AssertionError()
3667
+ return amount
3668
+
3669
+ def claim_reward_balance(
3670
+ self,
3671
+ reward_steem=0,
3672
+ reward_sbd=0,
3673
+ reward_hive=0,
3674
+ reward_hbd=0,
3675
+ reward_vests=0,
3676
+ account=None,
3677
+ **kwargs,
3678
+ ):
3679
+ """Claim reward balances.
3680
+ By default, this will claim ``all`` outstanding balances. To bypass
3681
+ this behaviour, set desired claim amount by setting any of
3682
+ `reward_steem`/``reward_hive, `reward_sbd`/``reward_hbd or `reward_vests`.
3683
+
3684
+ :param str reward_steem: Amount of STEEM you would like to claim.
3685
+ :param str reward_hive: Amount of HIVE you would like to claim.
3686
+ :param str reward_sbd: Amount of SBD you would like to claim.
3687
+ :param str reward_hbd: Amount of HBD you would like to claim.
3688
+ :param str reward_vests: Amount of VESTS you would like to claim.
3689
+ :param str account: The source account for the claim if not
3690
+ ``default_account`` is used.
3691
+
3692
+ """
3693
+ if account is None:
3694
+ account = self
3695
+ else:
3696
+ account = Account(account, blockchain_instance=self.blockchain)
3697
+ if not account:
3698
+ raise ValueError("You need to provide an account")
3699
+
3700
+ # if no values were set by user, claim all outstanding balances on
3701
+ # account
3702
+
3703
+ reward_token_amount = self._check_amount(
3704
+ reward_steem + reward_hive, self.blockchain.token_symbol
3705
+ )
3706
+ reward_backed_token_amount = self._check_amount(
3707
+ reward_sbd + reward_hbd, self.blockchain.backed_token_symbol
3708
+ )
3709
+ reward_vests_amount = self._check_amount(reward_vests, self.blockchain.vest_token_symbol)
3710
+
3711
+ if self.blockchain.is_hive:
3712
+ reward_token = "reward_hive"
3713
+ reward_backed_token = "reward_hbd"
3714
+ else:
3715
+ reward_token = "reward_steem"
3716
+ reward_backed_token = "reward_sbd"
3717
+
3718
+ if (
3719
+ reward_token_amount.amount == 0
3720
+ and reward_backed_token_amount.amount == 0
3721
+ and reward_vests_amount.amount == 0
3722
+ ):
3723
+ if len(account.balances["rewards"]) == 3:
3724
+ reward_token_amount = account.balances["rewards"][0]
3725
+ reward_backed_token_amount = account.balances["rewards"][1]
3726
+ reward_vests_amount = account.balances["rewards"][2]
3727
+ else:
3728
+ reward_token_amount = account.balances["rewards"][0]
3729
+ reward_vests_amount = account.balances["rewards"][1]
3730
+ if len(account.balances["rewards"]) == 3:
3731
+ op = operations.Claim_reward_balance(
3732
+ **{
3733
+ "account": account["name"],
3734
+ reward_token: reward_token_amount,
3735
+ reward_backed_token: reward_backed_token_amount,
3736
+ "reward_vests": reward_vests_amount,
3737
+ "prefix": self.blockchain.prefix,
3738
+ }
3739
+ )
3740
+ else:
3741
+ op = operations.Claim_reward_balance(
3742
+ **{
3743
+ "account": account["name"],
3744
+ reward_token: reward_token_amount,
3745
+ "reward_vests": reward_vests_amount,
3746
+ "prefix": self.blockchain.prefix,
3747
+ }
3748
+ )
3749
+
3750
+ return self.blockchain.finalizeOp(op, account, "posting", **kwargs)
3751
+
3752
+ def delegate_vesting_shares(self, to_account, vesting_shares, account=None, **kwargs):
3753
+ """Delegate SP to another account.
3754
+
3755
+ :param str to_account: Account we are delegating shares to
3756
+ (delegatee).
3757
+ :param str vesting_shares: Amount of VESTS to delegate eg. `10000
3758
+ VESTS`.
3759
+ :param str account: The source account (delegator). If not specified,
3760
+ ``default_account`` is used.
3761
+ """
3762
+ if account is None:
3763
+ account = self
3764
+ else:
3765
+ account = Account(account, blockchain_instance=self.blockchain)
3766
+ to_account = Account(to_account, blockchain_instance=self.blockchain)
3767
+ if to_account is None:
3768
+ raise ValueError("You need to provide a to_account")
3769
+ vesting_shares = self._check_amount(vesting_shares, self.blockchain.vest_token_symbol)
3770
+
3771
+ op = operations.Delegate_vesting_shares(
3772
+ **{
3773
+ "delegator": account["name"],
3774
+ "delegatee": to_account["name"],
3775
+ "vesting_shares": vesting_shares,
3776
+ "prefix": self.blockchain.prefix,
3777
+ }
3778
+ )
3779
+ return self.blockchain.finalizeOp(op, account, "active", **kwargs)
3780
+
3781
+ def withdraw_vesting(self, amount, account=None, **kwargs):
3782
+ """Withdraw VESTS from the vesting account.
3783
+
3784
+ :param float amount: number of VESTS to withdraw over a period of
3785
+ 13 weeks
3786
+ :param str account: (optional) the source account for the transfer
3787
+ if not ``default_account``
3788
+
3789
+ """
3790
+ if account is None:
3791
+ account = self
3792
+ else:
3793
+ account = Account(account, blockchain_instance=self.blockchain)
3794
+ amount = self._check_amount(amount, self.blockchain.vest_token_symbol)
3795
+
3796
+ op = operations.Withdraw_vesting(
3797
+ **{
3798
+ "account": account["name"],
3799
+ "vesting_shares": amount,
3800
+ "prefix": self.blockchain.prefix,
3801
+ }
3802
+ )
3803
+
3804
+ return self.blockchain.finalizeOp(op, account, "active", **kwargs)
3805
+
3806
+ def set_withdraw_vesting_route(
3807
+ self, to, percentage=100, account=None, auto_vest=False, **kwargs
3808
+ ):
3809
+ """Set up a vesting withdraw route. When vesting shares are
3810
+ withdrawn, they will be routed to these accounts based on the
3811
+ specified weights.
3812
+
3813
+ :param str to: Recipient of the vesting withdrawal
3814
+ :param float percentage: The percent of the withdraw to go
3815
+ to the 'to' account.
3816
+ :param str account: (optional) the vesting account
3817
+ :param bool auto_vest: Set to true if the 'to' account
3818
+ should receive the VESTS as VESTS, or false if it should
3819
+ receive them as STEEM. (defaults to ``False``)
3820
+
3821
+ """
3822
+ if account is None:
3823
+ account = self
3824
+ else:
3825
+ account = Account(account, blockchain_instance=self.blockchain)
3826
+ op = operations.Set_withdraw_vesting_route(
3827
+ **{
3828
+ "from_account": account["name"],
3829
+ "to_account": to,
3830
+ "percent": int(percentage * STEEM_1_PERCENT),
3831
+ "auto_vest": auto_vest,
3832
+ }
3833
+ )
3834
+
3835
+ return self.blockchain.finalizeOp(op, account, "active", **kwargs)
3836
+
3837
+ def allow(
3838
+ self, foreign, weight=None, permission="posting", account=None, threshold=None, **kwargs
3839
+ ):
3840
+ """Give additional access to an account by some other public
3841
+ key or account.
3842
+
3843
+ :param str foreign: The foreign account that will obtain access
3844
+ :param int weight: (optional) The weight to use. If not
3845
+ define, the threshold will be used. If the weight is
3846
+ smaller than the threshold, additional signatures will
3847
+ be required. (defaults to threshold)
3848
+ :param str permission: (optional) The actual permission to
3849
+ modify (defaults to ``posting``)
3850
+ :param str account: (optional) the account to allow access
3851
+ to (defaults to ``default_account``)
3852
+ :param int threshold: (optional) The threshold that needs to be
3853
+ reached by signatures to be able to interact
3854
+ """
3855
+ from copy import deepcopy
3856
+
3857
+ if account is None:
3858
+ account = self
3859
+ else:
3860
+ account = Account(account, blockchain_instance=self.blockchain)
3861
+
3862
+ if permission not in ["owner", "posting", "active"]:
3863
+ raise ValueError("Permission needs to be either 'owner', 'posting', or 'active")
3864
+ account = Account(account, blockchain_instance=self.blockchain)
3865
+
3866
+ if permission not in account:
3867
+ account = Account(account, blockchain_instance=self.blockchain, lazy=False, full=True)
3868
+ account.clear_cache()
3869
+ account.refresh()
3870
+ if permission not in account:
3871
+ account = Account(account, blockchain_instance=self.blockchain)
3872
+ if permission not in account:
3873
+ raise AssertionError("Could not access permission")
3874
+
3875
+ if not weight:
3876
+ weight = account[permission]["weight_threshold"]
3877
+
3878
+ authority = deepcopy(account[permission])
3879
+ try:
3880
+ pubkey = PublicKey(foreign, prefix=self.blockchain.prefix)
3881
+ authority["key_auths"].append([str(pubkey), weight])
3882
+ except:
3883
+ try:
3884
+ foreign_account = Account(foreign, blockchain_instance=self.blockchain)
3885
+ authority["account_auths"].append([foreign_account["name"], weight])
3886
+ except:
3887
+ raise ValueError("Unknown foreign account or invalid public key")
3888
+ if threshold:
3889
+ authority["weight_threshold"] = threshold
3890
+ self.blockchain._test_weights_treshold(authority)
3891
+
3892
+ op = operations.Account_update(
3893
+ **{
3894
+ "account": account["name"],
3895
+ permission: authority,
3896
+ "memo_key": account["memo_key"],
3897
+ "json_metadata": account["json_metadata"],
3898
+ "prefix": self.blockchain.prefix,
3899
+ }
3900
+ )
3901
+ if permission == "owner":
3902
+ return self.blockchain.finalizeOp(op, account, "owner", **kwargs)
3903
+ else:
3904
+ return self.blockchain.finalizeOp(op, account, "active", **kwargs)
3905
+
3906
+ def disallow(self, foreign, permission="posting", account=None, threshold=None, **kwargs):
3907
+ """Remove additional access to an account by some other public
3908
+ key or account.
3909
+
3910
+ :param str foreign: The foreign account that will obtain access
3911
+ :param str permission: (optional) The actual permission to
3912
+ modify (defaults to ``posting``)
3913
+ :param str account: (optional) the account to allow access
3914
+ to (defaults to ``default_account``)
3915
+ :param int threshold: The threshold that needs to be reached
3916
+ by signatures to be able to interact
3917
+ """
3918
+ if account is None:
3919
+ account = self
3920
+ else:
3921
+ account = Account(account, blockchain_instance=self.blockchain)
3922
+
3923
+ if permission not in ["owner", "active", "posting"]:
3924
+ raise ValueError("Permission needs to be either 'owner', 'posting', or 'active")
3925
+ authority = account[permission]
3926
+
3927
+ try:
3928
+ pubkey = PublicKey(foreign, prefix=self.blockchain.prefix)
3929
+ affected_items = list([x for x in authority["key_auths"] if x[0] == str(pubkey)])
3930
+ authority["key_auths"] = list(
3931
+ [x for x in authority["key_auths"] if x[0] != str(pubkey)]
3932
+ )
3933
+ except:
3934
+ try:
3935
+ foreign_account = Account(foreign, blockchain_instance=self.blockchain)
3936
+ affected_items = list(
3937
+ [x for x in authority["account_auths"] if x[0] == foreign_account["name"]]
3938
+ )
3939
+ authority["account_auths"] = list(
3940
+ [x for x in authority["account_auths"] if x[0] != foreign_account["name"]]
3941
+ )
3942
+ except:
3943
+ raise ValueError("Unknown foreign account or unvalid public key")
3944
+
3945
+ if not affected_items:
3946
+ raise ValueError("Changes nothing!")
3947
+ removed_weight = affected_items[0][1]
3948
+
3949
+ # Define threshold
3950
+ if threshold:
3951
+ authority["weight_threshold"] = threshold
3952
+
3953
+ # Correct threshold (at most by the amount removed from the
3954
+ # authority)
3955
+ try:
3956
+ self.blockchain._test_weights_treshold(authority)
3957
+ except:
3958
+ log.critical("The account's threshold will be reduced by %d" % (removed_weight))
3959
+ authority["weight_threshold"] -= removed_weight
3960
+ self.blockchain._test_weights_treshold(authority)
3961
+
3962
+ op = operations.Account_update(
3963
+ **{
3964
+ "account": account["name"],
3965
+ permission: authority,
3966
+ "memo_key": account["memo_key"],
3967
+ "json_metadata": account["json_metadata"],
3968
+ "prefix": self.blockchain.prefix,
3969
+ }
3970
+ )
3971
+ if permission == "owner":
3972
+ return self.blockchain.finalizeOp(op, account, "owner", **kwargs)
3973
+ else:
3974
+ return self.blockchain.finalizeOp(op, account, "active", **kwargs)
3975
+
3976
+ def feed_history(self, limit=None, start_author=None, start_permlink=None, account=None):
3977
+ """Stream the feed entries of an account in reverse time order.
3978
+
3979
+ .. note:: RPC nodes keep a limited history of entries for the
3980
+ user feed. Older entries may not be available via this
3981
+ call due to these node limitations.
3982
+
3983
+ :param int limit: (optional) stream the latest `limit`
3984
+ feed entries. If unset (default), all available entries
3985
+ are streamed.
3986
+ :param str start_author: (optional) start streaming the
3987
+ replies from this author. `start_permlink=None`
3988
+ (default) starts with the latest available entry.
3989
+ If set, `start_permlink` has to be set as well.
3990
+ :param str start_permlink: (optional) start streaming the
3991
+ replies from this permlink. `start_permlink=None`
3992
+ (default) starts with the latest available entry.
3993
+ If set, `start_author` has to be set as well.
3994
+ :param str account: (optional) the account to get replies
3995
+ to (defaults to ``default_account``)
3996
+
3997
+ comment_history_reverse example:
3998
+
3999
+ .. code-block:: python
4000
+
4001
+ from nectar.account import Account
4002
+ from nectar import Steem
4003
+ from nectar.nodelist import NodeList
4004
+ nodelist = NodeList()
4005
+ nodelist.update_nodes()
4006
+ stm = Steem(node=nodelist.get_hive_nodes())
4007
+ acc = Account("ned", blockchain_instance=stm)
4008
+ for reply in acc.feed_history(limit=10):
4009
+ print(reply)
4010
+
4011
+ """
4012
+ if limit is not None:
4013
+ if not isinstance(limit, integer_types) or limit <= 0:
4014
+ raise AssertionError("`limit` has to be greater than 0`")
4015
+ if (start_author is None and start_permlink is not None) or (
4016
+ start_author is not None and start_permlink is None
4017
+ ):
4018
+ raise AssertionError(
4019
+ "either both or none of `start_author` and `start_permlink` have to be set"
4020
+ )
4021
+
4022
+ if account is None:
4023
+ account = self
4024
+ else:
4025
+ account = Account(account, blockchain_instance=self.blockchain)
4026
+ feed_count = 0
4027
+ while True:
4028
+ query_limit = 100
4029
+ if limit is not None:
4030
+ query_limit = min(limit - feed_count + 1, query_limit)
4031
+ from .discussions import Discussions_by_feed, Query
4032
+
4033
+ query = Query(
4034
+ start_author=start_author,
4035
+ start_permlink=start_permlink,
4036
+ limit=query_limit,
4037
+ tag=account["name"],
4038
+ )
4039
+ results = Discussions_by_feed(query, blockchain_instance=self.blockchain)
4040
+ if len(results) == 0 or (start_permlink and len(results) == 1):
4041
+ return
4042
+ if feed_count > 0 and start_permlink:
4043
+ results = results[1:] # strip duplicates from previous iteration
4044
+ for entry in results:
4045
+ feed_count += 1
4046
+ yield entry
4047
+ start_permlink = entry["permlink"]
4048
+ start_author = entry["author"]
4049
+ if feed_count == limit:
4050
+ return
4051
+
4052
+ def blog_history(self, limit=None, start=-1, reblogs=True, account=None):
4053
+ """Stream the blog entries done by an account in reverse time order.
4054
+
4055
+ .. note:: RPC nodes keep a limited history of entries for the
4056
+ user blog. Older blog posts of an account may not be available
4057
+ via this call due to these node limitations.
4058
+
4059
+ :param int limit: (optional) stream the latest `limit`
4060
+ blog entries. If unset (default), all available blog
4061
+ entries are streamed.
4062
+ :param int start: (optional) start streaming the blog
4063
+ entries from this index. `start=-1` (default) starts
4064
+ with the latest available entry.
4065
+ :param bool reblogs: (optional) if set `True` (default)
4066
+ reblogs / resteems are included. If set `False`,
4067
+ reblogs/resteems are omitted.
4068
+ :param str account: (optional) the account to stream blog
4069
+ entries for (defaults to ``default_account``)
4070
+
4071
+ blog_history_reverse example:
4072
+
4073
+ .. code-block:: python
4074
+
4075
+ from nectar.account import Account
4076
+ from nectar import Steem
4077
+ from nectar.nodelist import NodeList
4078
+ nodelist = NodeList()
4079
+ nodelist.update_nodes()
4080
+ stm = Steem(node=nodelist.get_hive_nodes())
4081
+ acc = Account("steemitblog", blockchain_instance=stm)
4082
+ for post in acc.blog_history(limit=10):
4083
+ print(post)
4084
+
4085
+ """
4086
+ if limit is not None:
4087
+ if not isinstance(limit, integer_types) or limit <= 0:
4088
+ raise AssertionError("`limit` has to be greater than 0`")
4089
+
4090
+ if account is None:
4091
+ account = self
4092
+ else:
4093
+ account = Account(account, blockchain_instance=self.blockchain)
4094
+
4095
+ post_count = 0
4096
+ start_permlink = None
4097
+ start_author = None
4098
+ while True:
4099
+ query_limit = 100
4100
+ if limit is not None and reblogs:
4101
+ query_limit = min(limit - post_count + 1, query_limit)
4102
+
4103
+ from .discussions import Discussions_by_blog
4104
+
4105
+ query = {
4106
+ "start_author": start_author,
4107
+ "start_permlink": start_permlink,
4108
+ "limit": query_limit,
4109
+ "tag": account["name"],
4110
+ }
4111
+ results = Discussions_by_blog(query, blockchain_instance=self.blockchain)
4112
+ if len(results) == 0 or (start_permlink and len(results) == 1):
4113
+ return
4114
+ if start_permlink:
4115
+ results = results[1:] # strip duplicates from previous iteration
4116
+ for post in results:
4117
+ if post["author"] == "":
4118
+ continue
4119
+ if reblogs or post["author"] == account["name"]:
4120
+ post_count += 1
4121
+ yield post
4122
+ start_permlink = post["permlink"]
4123
+ start_author = post["author"]
4124
+ if post_count == limit:
4125
+ return
4126
+
4127
+ def comment_history(self, limit=None, start_permlink=None, account=None):
4128
+ """Stream the comments done by an account in reverse time order.
4129
+
4130
+ .. note:: RPC nodes keep a limited history of user comments for the
4131
+ user feed. Older comments may not be available via this
4132
+ call due to these node limitations.
4133
+
4134
+ :param int limit: (optional) stream the latest `limit`
4135
+ comments. If unset (default), all available comments
4136
+ are streamed.
4137
+ :param str start_permlink: (optional) start streaming the
4138
+ comments from this permlink. `start_permlink=None`
4139
+ (default) starts with the latest available entry.
4140
+ :param str account: (optional) the account to stream
4141
+ comments for (defaults to ``default_account``)
4142
+
4143
+ comment_history_reverse example:
4144
+
4145
+ .. code-block:: python
4146
+
4147
+ from nectar.account import Account
4148
+ from nectar import Steem
4149
+ from nectar.nodelist import NodeList
4150
+ nodelist = NodeList()
4151
+ nodelist.update_nodes()
4152
+ stm = Steem(node=nodelist.get_hive_nodes())
4153
+ acc = Account("ned", blockchain_instance=stm)
4154
+ for comment in acc.comment_history(limit=10):
4155
+ print(comment)
4156
+
4157
+ """
4158
+ if limit is not None:
4159
+ if not isinstance(limit, integer_types) or limit <= 0:
4160
+ raise AssertionError("`limit` has to be greater than 0`")
4161
+
4162
+ if account is None:
4163
+ account = self
4164
+ else:
4165
+ account = Account(account, blockchain_instance=self.blockchain)
4166
+
4167
+ comment_count = 0
4168
+ while True:
4169
+ query_limit = 100
4170
+ if limit is not None:
4171
+ query_limit = min(limit - comment_count + 1, query_limit)
4172
+ from .discussions import Discussions_by_comments
4173
+
4174
+ query = {
4175
+ "start_author": account["name"],
4176
+ "start_permlink": start_permlink,
4177
+ "limit": query_limit,
4178
+ }
4179
+ results = Discussions_by_comments(query, blockchain_instance=self.blockchain)
4180
+ if len(results) == 0 or (start_permlink and len(results) == 1):
4181
+ return
4182
+ if comment_count > 0 and start_permlink:
4183
+ results = results[1:] # strip duplicates from previous iteration
4184
+ for comment in results:
4185
+ if comment["permlink"] == "":
4186
+ continue
4187
+ comment_count += 1
4188
+ yield comment
4189
+ start_permlink = comment["permlink"]
4190
+ if comment_count == limit:
4191
+ return
4192
+
4193
+ def reply_history(self, limit=None, start_author=None, start_permlink=None, account=None):
4194
+ """Stream the replies to an account in reverse time order.
4195
+
4196
+ .. note:: RPC nodes keep a limited history of entries for the
4197
+ replies to an author. Older replies to an account may
4198
+ not be available via this call due to
4199
+ these node limitations.
4200
+
4201
+ :param int limit: (optional) stream the latest `limit`
4202
+ replies. If unset (default), all available replies
4203
+ are streamed.
4204
+ :param str start_author: (optional) start streaming the
4205
+ replies from this author. `start_permlink=None`
4206
+ (default) starts with the latest available entry.
4207
+ If set, `start_permlink` has to be set as well.
4208
+ :param str start_permlink: (optional) start streaming the
4209
+ replies from this permlink. `start_permlink=None`
4210
+ (default) starts with the latest available entry.
4211
+ If set, `start_author` has to be set as well.
4212
+ :param str account: (optional) the account to get replies
4213
+ to (defaults to ``default_account``)
4214
+
4215
+ comment_history_reverse example:
4216
+
4217
+ .. code-block:: python
4218
+
4219
+ from nectar.account import Account
4220
+ acc = Account("ned")
4221
+ for reply in acc.reply_history(limit=10):
4222
+ print(reply)
4223
+
4224
+ """
4225
+ if limit is not None:
4226
+ if not isinstance(limit, integer_types) or limit <= 0:
4227
+ raise AssertionError("`limit` has to be greater than 0`")
4228
+ if (start_author is None and start_permlink is not None) or (
4229
+ start_author is not None and start_permlink is None
4230
+ ):
4231
+ raise AssertionError(
4232
+ "either both or none of `start_author` and `start_permlink` have to be set"
4233
+ )
4234
+
4235
+ if account is None:
4236
+ account = self
4237
+ else:
4238
+ account = Account(account, blockchain_instance=self.blockchain)
4239
+
4240
+ if start_author is None:
4241
+ start_author = account["name"]
4242
+
4243
+ reply_count = 0
4244
+ while True:
4245
+ query_limit = 100
4246
+ if limit is not None:
4247
+ query_limit = min(limit - reply_count + 1, query_limit)
4248
+ from .discussions import Replies_by_last_update
4249
+
4250
+ query = {
4251
+ "start_author": start_author,
4252
+ "start_permlink": start_permlink,
4253
+ "limit": query_limit,
4254
+ }
4255
+ results = Replies_by_last_update(query, blockchain_instance=self.blockchain)
4256
+ if len(results) == 0 or (start_permlink and len(results) == 1):
4257
+ return
4258
+ if reply_count > 0 and start_permlink:
4259
+ results = results[1:] # strip duplicates from previous iteration
4260
+ for reply in results:
4261
+ if reply["author"] == "":
4262
+ continue
4263
+ reply_count += 1
4264
+ yield reply
4265
+ start_author = reply["author"]
4266
+ start_permlink = reply["permlink"]
4267
+ if reply_count == limit:
4268
+ return
4269
+
4270
+
4271
+ class AccountsObject(list):
4272
+ def printAsTable(self):
4273
+ t = PrettyTable(["Name"])
4274
+ t.align = "l"
4275
+ for acc in self:
4276
+ t.add_row([acc["name"]])
4277
+ print(t)
4278
+
4279
+ def print_summarize_table(self, tag_type="Follower", return_str=False, **kwargs):
4280
+ t = PrettyTable(["Key", "Value"])
4281
+ t.align = "r"
4282
+ t.add_row([tag_type + " count", str(len(self))])
4283
+ own_mvest = []
4284
+ eff_sp = []
4285
+ rep = []
4286
+ last_vote_h = []
4287
+ last_post_d = []
4288
+ no_vote = 0
4289
+ no_post = 0
4290
+ for f in self:
4291
+ rep.append(f.rep)
4292
+ own_mvest.append(float(f.balances["available"][2]) / 1e6)
4293
+ eff_sp.append(f.get_token_power())
4294
+ last_vote = addTzInfo(datetime.now(timezone.utc)) - (f["last_vote_time"])
4295
+ if last_vote.days >= 365:
4296
+ no_vote += 1
4297
+ else:
4298
+ last_vote_h.append(last_vote.total_seconds() / 60 / 60)
4299
+ last_post = addTzInfo(datetime.now(timezone.utc)) - (f["last_root_post"])
4300
+ if last_post.days >= 365:
4301
+ no_post += 1
4302
+ else:
4303
+ last_post_d.append(last_post.total_seconds() / 60 / 60 / 24)
4304
+
4305
+ t.add_row(["Summed MVest value", "%.2f" % sum(own_mvest)])
4306
+ if len(rep) > 0:
4307
+ t.add_row(["Mean Rep.", "%.2f" % (sum(rep) / len(rep))])
4308
+ t.add_row(["Max Rep.", "%.2f" % (max(rep))])
4309
+ if len(eff_sp) > 0:
4310
+ t.add_row(["Summed eff. SP", "%.2f" % sum(eff_sp)])
4311
+ t.add_row(["Mean eff. SP", "%.2f" % (sum(eff_sp) / len(eff_sp))])
4312
+ t.add_row(["Max eff. SP", "%.2f" % max(eff_sp)])
4313
+ if len(last_vote_h) > 0:
4314
+ t.add_row(
4315
+ ["Mean last vote diff in hours", "%.2f" % (sum(last_vote_h) / len(last_vote_h))]
4316
+ )
4317
+ if len(last_post_d) > 0:
4318
+ t.add_row(
4319
+ ["Mean last post diff in days", "%.2f" % (sum(last_post_d) / len(last_post_d))]
4320
+ )
4321
+ t.add_row([tag_type + " without vote in 365 days", no_vote])
4322
+ t.add_row([tag_type + " without post in 365 days", no_post])
4323
+ if return_str:
4324
+ return t.get_string(**kwargs)
4325
+ else:
4326
+ print(t.get_string(**kwargs))
4327
+
4328
+
4329
+ class Accounts(AccountsObject):
4330
+ """Obtain a list of accounts
4331
+
4332
+ :param list name_list: list of accounts to fetch
4333
+ :param int batch_limit: (optional) maximum number of accounts
4334
+ to fetch per call, defaults to 100
4335
+ :param Steem/Hive blockchain_instance: Steem() or Hive() instance to use when
4336
+ accessing a RPCcreator = Account(creator, blockchain_instance=self)
4337
+ """
4338
+
4339
+ def __init__(
4340
+ self, name_list, batch_limit=100, lazy=False, full=True, blockchain_instance=None, **kwargs
4341
+ ):
4342
+ if blockchain_instance is None:
4343
+ if kwargs.get("steem_instance"):
4344
+ blockchain_instance = kwargs["steem_instance"]
4345
+ elif kwargs.get("hive_instance"):
4346
+ blockchain_instance = kwargs["hive_instance"]
4347
+ self.blockchain = blockchain_instance or shared_blockchain_instance()
4348
+
4349
+ if not self.blockchain.is_connected():
4350
+ return
4351
+ accounts = []
4352
+ name_cnt = 0
4353
+
4354
+ while name_cnt < len(name_list):
4355
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
4356
+ if self.blockchain.rpc.get_use_appbase():
4357
+ accounts += self.blockchain.rpc.find_accounts(
4358
+ {"accounts": name_list[name_cnt : batch_limit + name_cnt]}, api="database"
4359
+ )["accounts"]
4360
+ else:
4361
+ accounts += self.blockchain.rpc.get_accounts(
4362
+ name_list[name_cnt : batch_limit + name_cnt]
4363
+ )
4364
+ name_cnt += batch_limit
4365
+
4366
+ super(Accounts, self).__init__(
4367
+ [
4368
+ Account(x, lazy=lazy, full=full, blockchain_instance=self.blockchain)
4369
+ for x in accounts
4370
+ ]
4371
+ )