symbol-sdk-python 3.3.0__py3-none-any.whl → 3.3.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: symbol-sdk-python
3
- Version: 3.3.0
3
+ Version: 3.3.2
4
4
  Summary: Symbol SDK
5
5
  Home-page: https://github.com/symbol/symbol/tree/main/sdk/python
6
6
  License: MIT
@@ -9,17 +9,17 @@ Author: Symbol Contributors
9
9
  Author-email: contributors@symbol.dev
10
10
  Maintainer: Symbol Contributors
11
11
  Maintainer-email: contributors@symbol.dev
12
- Requires-Python: >=3.9.2,<4.0.0
12
+ Requires-Python: >=3.10,<4.0
13
13
  Classifier: License :: OSI Approved :: MIT License
14
14
  Classifier: Programming Language :: Python :: 3
15
15
  Classifier: Programming Language :: Python :: 3.10
16
16
  Classifier: Programming Language :: Python :: 3.11
17
17
  Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: 3.13
19
- Classifier: Programming Language :: Python :: 3.9
20
- Requires-Dist: cryptography (>=46.0.1,<46.1.0)
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Requires-Dist: cryptography (>=49.0.0,<49.1.0)
21
21
  Requires-Dist: mnemonic (>=0.20,<1.0)
22
- Requires-Dist: pillow (>=11.3.0,<11.4.0)
22
+ Requires-Dist: pillow (>=12.2.0,<12.3.0)
23
23
  Requires-Dist: pynacl (>=1.6.0,<1.7.0)
24
24
  Requires-Dist: pyyaml (>=6.0.1,<6.1.0)
25
25
  Requires-Dist: pyzbar (>=0.1.9,<0.2.0)
@@ -1,5 +1,5 @@
1
1
  symbolchain/AccountDescriptorRepository.py,sha256=XCIIt6XvxqClQL0R7_-xyFBhb0q9bIUbNTr5-YS5f1o,2508
2
- symbolchain/ArrayHelpers.py,sha256=Iu5QqlWW5j_6Qd0oTFO41MfDH_mKwQLT7XeD2frU2mw,3728
2
+ symbolchain/ArrayHelpers.py,sha256=AI3zBv4KKyJ4gxzAFwRgKNVVUs38PxJEn36SKpmTIaI,3612
3
3
  symbolchain/BaseValue.py,sha256=vc56rclpJtxYIL6Mvo6KWwnA0pUfhtsB71NSEjobQtc,1636
4
4
  symbolchain/Bip32.py,sha256=L8Fs0XTERGWSPX972KUn-AOPwAS2gZMdVTKAq4HOGa8,1787
5
5
  symbolchain/BlockchainSettings.py,sha256=uRHHxi0gW_U9LFUhoDpNMTe2CobLNJQwE9ZMy2o1aX8,723
@@ -17,7 +17,7 @@ symbolchain/Ordered.py,sha256=--tQ_aZpXhZYcsRLgMjUNHPYlAH7BMfuss2CSxMvNhY,397
17
17
  symbolchain/PrivateKeyStorage.py,sha256=dACQnqY1nDtNbfItdrG1iyqbnNnEv8G_sntDCD5crs8,1606
18
18
  symbolchain/QrSignatureStorage.py,sha256=-KM2QdzkBwOrydxyTLttdDBcZ4vk0O-gwZ51PWdwEDQ,1348
19
19
  symbolchain/QrStorage.py,sha256=ch1XsJ-zkZ27hYd_I_ZfChuIyp-k41R3tbTb8LHy6Ik,1494
20
- symbolchain/RuleBasedTransactionFactory.py,sha256=a07brGtzujd3I_DDAiQmprgh4a-hoEET2SjMGX-Pphk,5101
20
+ symbolchain/RuleBasedTransactionFactory.py,sha256=Bd-b3DFmTeEuZrTKwKHGO7Y38FnlfHt65TEEcF3kNRI,5526
21
21
  symbolchain/SharedKey.py,sha256=cnVwHLznU7RKcqhxDcLc9QfRwSHI9Fm9tzanSjsnQsQ,593
22
22
  symbolchain/TransactionDescriptorProcessor.py,sha256=XeEuWs8EFGKV8SBDm_tG9KWMjWMsvL8G4PHJrTl57uE,1806
23
23
  symbolchain/Transforms.py,sha256=NtSWiq3ntvylwq1VoQ4S1OIeNnVOBekWDK-Vf2uo-ZI,226
@@ -31,24 +31,27 @@ symbolchain/facade/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
31
31
  symbolchain/impl/CipherHelpers.py,sha256=KSYH95oie-PxFfGAYfnqHOC1RWIrdxC7Vp6Go-o469Q,2152
32
32
  symbolchain/impl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
33
  symbolchain/nc/__init__.py,sha256=ROTctT787pDzqlnJkavy5vAmVe_JLUwj5e9GprFklXM,121192
34
+ symbolchain/nem/FeeCalculator.py,sha256=EnQinmAivWVEIaidLDhkx8TiHYvGEDoNt7xMSIekV_8,3860
34
35
  symbolchain/nem/KeyPair.py,sha256=34VwU7EdroB0nnzTCdTEUAK_DMrcDYzo3JKdniXQMG4,3712
35
36
  symbolchain/nem/MessageEncoder.py,sha256=EGm4mtmyXUXQb-R49Xmrl3w40ye_LbZQ8r5aJusH0WE,2699
36
37
  symbolchain/nem/Network.py,sha256=vD7YEqJHyUPK14UAGBT29cX9Cjvq1uHDfeiYudaxgAw,1746
37
38
  symbolchain/nem/SharedKey.py,sha256=owBUV_XNo1wCby0b4Amz1Bl6uzTeQmiSFy7UcWFo00I,1094
38
- symbolchain/nem/TransactionFactory.py,sha256=IaKsLdTmW8ZU9s5kaUcaGl7P91awR8BMDfX_zWTAO04,4170
39
+ symbolchain/nem/TransactionFactory.py,sha256=UKc54dTiT7Q8jYcIOWcv0uT1bkE_05okevY_nnx4xDE,4103
39
40
  symbolchain/nem/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
41
  symbolchain/ripemd160.py,sha256=u2uv8wJY5FY8tll_EfiGE5o65Ri6E-8HbynvJUPBJa8,429
41
42
  symbolchain/sc/__init__.py,sha256=Po_ezk7y9Rr_xESvHExyq420FRro5un2KvSnojVf3hg,317964
42
- symbolchain/symbol/IdGenerator.py,sha256=4CWW5EekbmULDDukscs73ZFN_U179hAacK3luL7Q0TA,1696
43
+ symbolchain/symbol/FeeCalculator.py,sha256=BmWjyGZeAJEW6LJpT2l8aXQcB4KongfKtzQ6CvO1k-0,280
44
+ symbolchain/symbol/IdGenerator.py,sha256=_QCFDtdVrrlEpT7PEOtCtcv-WaLbyi7rg0F9M1JRRNk,1957
43
45
  symbolchain/symbol/KeyPair.py,sha256=CkQh7oOeVRbjKewTfln0t1PZckcTuIXb2roY1zZjVrc,1506
44
46
  symbolchain/symbol/Merkle.py,sha256=FrCwnveXEoXEx7xFP4UC_htjAVuVeUNbvnh8YDdy3z0,7243
45
47
  symbolchain/symbol/MessageEncoder.py,sha256=X4io7OTEJOgy9LtDCzInm4QQFVbNWpkJnba6-S5vrkI,3882
46
48
  symbolchain/symbol/Metadata.py,sha256=8DpgwTFy8IYxjNRmT_NBSn3WBhU9z2xlWvsMIh7GgwU,884
47
- symbolchain/symbol/Network.py,sha256=CFFEFEByLDF-KFD2As5dgj-1MIuKmec5T_yfoxNDHkQ,2933
49
+ symbolchain/symbol/Network.py,sha256=rMAOi1g3rDrzwrJol4k_RlnBMu7xqERa7xDRic2UtEc,3036
50
+ symbolchain/symbol/Restriction.py,sha256=8U5N7bDs7t1vD8ztgGpY6Skhx-NNp8ilK0wOwTtF0Fw,265
48
51
  symbolchain/symbol/SharedKey.py,sha256=Bg1qYW_iMNYKtokep5S1m9b7GfywM5OGaCrVUdt01JU,334
49
- symbolchain/symbol/TransactionFactory.py,sha256=65N6UHDwrzNyHnpxkw-RmjuC32Znto1B9TH5OgcD1Oo,3957
52
+ symbolchain/symbol/TransactionFactory.py,sha256=YkQC2yBI5pSuSTc7-Aivh9kIKt32-Ho9XYWbbye2syw,4131
50
53
  symbolchain/symbol/VotingKeysGenerator.py,sha256=tuJ6yl2CqqQ9uupAw3z-GJIW20WOsblcLQb2_fPUCDE,1686
51
54
  symbolchain/symbol/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
- symbol_sdk_python-3.3.0.dist-info/METADATA,sha256=wywxCNiAGcO_Ox2xEqfS7zfoSUJQgL5AVJFQ-3yznME,5709
53
- symbol_sdk_python-3.3.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
54
- symbol_sdk_python-3.3.0.dist-info/RECORD,,
55
+ symbol_sdk_python-3.3.2.dist-info/METADATA,sha256=uFFkPwbCBMS07zX1E3xPmtuH6s_y3fOIPSzFY368EdA,5707
56
+ symbol_sdk_python-3.3.2.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
57
+ symbol_sdk_python-3.3.2.dist-info/RECORD,,
@@ -1,116 +1,116 @@
1
- def read_array_impl(view, factory_class, accessor, should_continue):
2
- elements = []
3
- previous_element = None
4
-
5
- i = 0
6
- while should_continue(i, view):
7
- element = factory_class.deserialize(view)
8
-
9
- if element.size <= 0:
10
- raise ValueError('element size has invalid size')
11
-
12
- if accessor and previous_element and accessor(previous_element) >= accessor(element):
13
- raise ValueError('elements in array are not sorted')
14
-
15
- elements.append(element)
16
- view = view[element.size:]
17
-
18
- previous_element = element
19
- i += 1
20
-
21
- return elements
22
-
23
-
24
- def write_array_impl(elements, count, accessor):
25
- output_buffer = bytes()
26
- for i in range(0, count):
27
- element = elements[i]
28
- if accessor and i > 0 and accessor(elements[i - 1]) >= accessor(element):
29
- raise ValueError('array passed to write array is not sorted')
30
-
31
- output_buffer += element.serialize()
32
-
33
- return output_buffer
34
-
35
-
36
- class ArrayHelpers:
37
- @staticmethod
38
- def get_bytes(view, size):
39
- """Returns first size bytes of view."""
40
- if size > len(view):
41
- raise ValueError(f'size should not exceed {len(view)}. The value of size was: {size}.')
42
-
43
- return view[:size].tobytes()
44
-
45
- @staticmethod
46
- def align_up(size, alignment):
47
- """Calculates aligned size."""
48
- return (size + alignment - 1) // alignment * alignment
49
-
50
- @staticmethod
51
- def size(elements, alignment=0, skip_last_element_padding=False):
52
- """Calculates size of variable size objects."""
53
- if not alignment:
54
- return sum(map(lambda e: e.size, elements))
55
-
56
- if not skip_last_element_padding:
57
- return sum(map(lambda e: ArrayHelpers.align_up(e.size, alignment), elements))
58
-
59
- return sum(map(lambda e: ArrayHelpers.align_up(e.size, alignment), elements[:-1])) + sum(map(lambda e: e.size, elements[-1:]))
60
-
61
- @staticmethod
62
- def read_array(view, factory_class, accessor=None):
63
- """Reads array of objects."""
64
- return read_array_impl(view, factory_class, accessor, lambda _, view: len(view) > 0)
65
-
66
- @staticmethod
67
- def read_array_count(view, factory_class, count, accessor=None):
68
- """Reads array of deterministic number of objects."""
69
- return read_array_impl(view, factory_class, accessor, lambda index, _: count > index)
70
-
71
- @staticmethod
72
- def read_variable_size_elements(view, factory_class, alignment, skip_last_element_padding=False):
73
- """Reads array of variable size objects."""
74
- elements = []
75
- while len(view) > 0:
76
- element = factory_class.deserialize(view)
77
-
78
- if element.size <= 0:
79
- raise ValueError('element size has invalid size')
80
-
81
- elements.append(element)
82
-
83
- aligned_size = ArrayHelpers.align_up(element.size, alignment)
84
- if skip_last_element_padding and element.size >= len(view):
85
- aligned_size = element.size
86
-
87
- if aligned_size > len(view):
88
- raise ValueError('unexpected buffer length')
89
-
90
- view = view[aligned_size:]
91
-
92
- return elements
93
-
94
- @staticmethod
95
- def write_array(elements, accessor=None):
96
- """Writes array of objects."""
97
- return write_array_impl(elements, len(elements), accessor)
98
-
99
- @staticmethod
100
- def write_array_count(elements, count, accessor=None):
101
- """Writes array of deterministic number of objects."""
102
- return write_array_impl(elements, count, accessor)
103
-
104
- @staticmethod
105
- def write_variable_size_elements(elements, alignment, skip_last_element_padding=False):
106
- """Writes array of variable size objects."""
107
- output_buffer = bytes()
108
- for index, element in enumerate(elements):
109
- output_buffer += element.serialize()
110
-
111
- if not skip_last_element_padding or len(elements) - 1 != index:
112
- aligned_size = ArrayHelpers.align_up(element.size, alignment)
113
- if aligned_size != element.size:
114
- output_buffer += bytes(aligned_size - element.size)
115
-
116
- return output_buffer
1
+ def read_array_impl(view, factory_class, accessor, should_continue):
2
+ elements = []
3
+ previous_element = None
4
+
5
+ i = 0
6
+ while should_continue(i, view):
7
+ element = factory_class.deserialize(view)
8
+
9
+ if element.size <= 0:
10
+ raise ValueError('element size has invalid size')
11
+
12
+ if accessor and previous_element and accessor(previous_element) >= accessor(element):
13
+ raise ValueError('elements in array are not sorted')
14
+
15
+ elements.append(element)
16
+ view = view[element.size:]
17
+
18
+ previous_element = element
19
+ i += 1
20
+
21
+ return elements
22
+
23
+
24
+ def write_array_impl(elements, count, accessor):
25
+ output_buffer = bytes()
26
+ for i in range(0, count):
27
+ element = elements[i]
28
+ if accessor and i > 0 and accessor(elements[i - 1]) >= accessor(element):
29
+ raise ValueError('array passed to write array is not sorted')
30
+
31
+ output_buffer += element.serialize()
32
+
33
+ return output_buffer
34
+
35
+
36
+ class ArrayHelpers:
37
+ @staticmethod
38
+ def get_bytes(view, size):
39
+ """Returns first size bytes of view."""
40
+ if size > len(view):
41
+ raise ValueError(f'size should not exceed {len(view)}. The value of size was: {size}.')
42
+
43
+ return view[:size].tobytes()
44
+
45
+ @staticmethod
46
+ def align_up(size, alignment):
47
+ """Calculates aligned size."""
48
+ return (size + alignment - 1) // alignment * alignment
49
+
50
+ @staticmethod
51
+ def size(elements, alignment=0, skip_last_element_padding=False):
52
+ """Calculates size of variable size objects."""
53
+ if not alignment:
54
+ return sum(map(lambda e: e.size, elements))
55
+
56
+ if not skip_last_element_padding:
57
+ return sum(map(lambda e: ArrayHelpers.align_up(e.size, alignment), elements))
58
+
59
+ return sum(map(lambda e: ArrayHelpers.align_up(e.size, alignment), elements[:-1])) + sum(map(lambda e: e.size, elements[-1:]))
60
+
61
+ @staticmethod
62
+ def read_array(view, factory_class, accessor=None):
63
+ """Reads array of objects."""
64
+ return read_array_impl(view, factory_class, accessor, lambda _, view: len(view) > 0)
65
+
66
+ @staticmethod
67
+ def read_array_count(view, factory_class, count, accessor=None):
68
+ """Reads array of deterministic number of objects."""
69
+ return read_array_impl(view, factory_class, accessor, lambda index, _: count > index)
70
+
71
+ @staticmethod
72
+ def read_variable_size_elements(view, factory_class, alignment, skip_last_element_padding=False):
73
+ """Reads array of variable size objects."""
74
+ elements = []
75
+ while len(view) > 0:
76
+ element = factory_class.deserialize(view)
77
+
78
+ if element.size <= 0:
79
+ raise ValueError('element size has invalid size')
80
+
81
+ elements.append(element)
82
+
83
+ aligned_size = ArrayHelpers.align_up(element.size, alignment)
84
+ if skip_last_element_padding and element.size >= len(view):
85
+ aligned_size = element.size
86
+
87
+ if aligned_size > len(view):
88
+ raise ValueError('unexpected buffer length')
89
+
90
+ view = view[aligned_size:]
91
+
92
+ return elements
93
+
94
+ @staticmethod
95
+ def write_array(elements, accessor=None):
96
+ """Writes array of objects."""
97
+ return write_array_impl(elements, len(elements), accessor)
98
+
99
+ @staticmethod
100
+ def write_array_count(elements, count, accessor=None):
101
+ """Writes array of deterministic number of objects."""
102
+ return write_array_impl(elements, count, accessor)
103
+
104
+ @staticmethod
105
+ def write_variable_size_elements(elements, alignment, skip_last_element_padding=False):
106
+ """Writes array of variable size objects."""
107
+ output_buffer = bytes()
108
+ for index, element in enumerate(elements):
109
+ output_buffer += element.serialize()
110
+
111
+ if not skip_last_element_padding or len(elements) - 1 != index:
112
+ aligned_size = ArrayHelpers.align_up(element.size, alignment)
113
+ if aligned_size != element.size:
114
+ output_buffer += bytes(aligned_size - element.size)
115
+
116
+ return output_buffer
@@ -161,6 +161,17 @@ class RuleBasedTransactionFactory:
161
161
 
162
162
  @staticmethod
163
163
  def _auto_encode_strings(entity):
164
+ if not hasattr(entity, '__dict__'): # skip non-objects
165
+ return
166
+
164
167
  for key, value in vars(entity).items():
168
+ if '_' == key[0] and '_' == key[-1]: # skip system properties
169
+ continue
170
+
165
171
  if isinstance(value, str):
166
- setattr(entity, key, value.encode('utf8'))
172
+ setattr(entity, key, value.encode('utf8')) # convert string literals to byte array
173
+ elif hasattr(value, '__len__'): # process list items
174
+ for item in value:
175
+ RuleBasedTransactionFactory._auto_encode_strings(item)
176
+ else:
177
+ RuleBasedTransactionFactory._auto_encode_strings(value) # process nested objects
@@ -0,0 +1,94 @@
1
+ import math
2
+
3
+ from symbolchain.nc import TransactionType
4
+
5
+
6
+ def calculate_mosaic_rental_fee():
7
+ """Calculates the minimum required mosaic rental fee."""
8
+
9
+ return 10 * 1_000_000
10
+
11
+
12
+ def calculate_namespace_rental_fee(is_root):
13
+ """
14
+ Calculates the minimum required namespace rental fee.
15
+ When is_root is True, calculates the root namespace fee.
16
+ Otherwise, calculates the child namespace fee.
17
+ """
18
+
19
+ return (100 if is_root else 10) * 1_000_000
20
+
21
+
22
+ def _decode_mosaic_id(mosaic_id):
23
+ return {'namespace_id': {'name': mosaic_id.namespace_id.name.decode('utf8')}, 'name': mosaic_id.name.decode('utf8')}
24
+
25
+
26
+ def _calculate_unweighted_transfer_fee(transaction, mosaic_information_lookup):
27
+ xem_supply = 8_999_999_999
28
+ max_mosaic_units = 9_000_000_000_000_000
29
+
30
+ def _calculate_xem_transfer_fee(amount):
31
+ return int(min(25, max(1, amount // 10000)))
32
+
33
+ def _calculate_mosaic_total_quantity(mosaic_information):
34
+ return mosaic_information['supply'] * (10 ** mosaic_information['divisibility'])
35
+
36
+ def _calculate_xem_equivalent(amount, mosaic_amount, mosaic_information):
37
+ if 0 == mosaic_information['supply']:
38
+ return 0
39
+
40
+ # amount XEM whole units
41
+ # mosaic_amount mosaic atomic units
42
+ # xem_supply / _calculate_mosaic_total_quantity(mosaic_information) convert mosaic_amount from mosaic units to XEM equivalent units
43
+ return amount * mosaic_amount * xem_supply / _calculate_mosaic_total_quantity(mosaic_information)
44
+
45
+ def _calculate_mosaic_transfer_fee(amount, mosaic, mosaic_information):
46
+ if 0 == mosaic_information['divisibility'] and 10_000 >= mosaic_information['supply']:
47
+ return 1
48
+
49
+ xem_equivalent = _calculate_xem_equivalent(amount, mosaic.mosaic.amount.value, mosaic_information)
50
+ xem_fee = _calculate_xem_transfer_fee(xem_equivalent)
51
+ mosaic_total_quantity = _calculate_mosaic_total_quantity(mosaic_information)
52
+ supply_related_adjustment = 0 if 0 == mosaic_total_quantity else int(0.8 * math.log(max_mosaic_units / mosaic_total_quantity))
53
+ return max(1, xem_fee - supply_related_adjustment)
54
+
55
+ message_fee = 0 if not transaction.message else len(transaction.message.message) // 32 + 1
56
+ amount = transaction.amount.value // 1_000_000 # convert to XEM whole units
57
+ if not transaction.mosaics:
58
+ transfer_fee = _calculate_xem_transfer_fee(amount)
59
+ return message_fee + transfer_fee
60
+
61
+ def _lookup_and_calculate_mosaic_transfer_fee(mosaic):
62
+ mosaic_id = _decode_mosaic_id(mosaic.mosaic.mosaic_id)
63
+ mosaic_information = mosaic_information_lookup(mosaic_id)
64
+ if not mosaic_information:
65
+ raise ValueError(f'unable to find fee information for {mosaic_id["namespace_id"]["name"]}:{mosaic_id["name"]}')
66
+
67
+ return _calculate_mosaic_transfer_fee(amount, mosaic, mosaic_information)
68
+
69
+ transfer_fee = sum(_lookup_and_calculate_mosaic_transfer_fee(mosaic) for mosaic in transaction.mosaics)
70
+ return message_fee + transfer_fee
71
+
72
+
73
+ def calculate_transaction_fee(transaction, mosaic_information_lookup=None):
74
+ """
75
+ Calculates the minimum required transaction fee for a transaction.
76
+ mosaic_information_lookup looks up mosaic information ({supply, divisibility}) given mosaic identifier.
77
+ When a function, mosaic identifier will be passed as parameter.
78
+ When an object map, fully qualified mosaic identifier will be used as an index.
79
+ When None, this function will be unable to calculate fees for custom mosaic transfers.
80
+ """
81
+
82
+ def _weight_with_fee_unit(amount):
83
+ return amount * 50_000
84
+
85
+ if TransactionType.TRANSFER != transaction.type_:
86
+ return _weight_with_fee_unit(10 if TransactionType.MULTISIG_ACCOUNT_MODIFICATION == transaction.type_ else 3)
87
+
88
+ def _make_lookup_function(lookup):
89
+ if callable(lookup):
90
+ return lookup
91
+
92
+ return lambda mosaic_id: lookup[f'{mosaic_id["namespace_id"]["name"]}:{mosaic_id["name"]}']
93
+
94
+ return _weight_with_fee_unit(_calculate_unweighted_transfer_fee(transaction, _make_lookup_function(mosaic_information_lookup)))
@@ -33,10 +33,6 @@ class TransactionFactory:
33
33
  if autosort:
34
34
  transaction.sort()
35
35
 
36
- # hack: explicitly translate transfer message
37
- if nc.TransactionType.TRANSFER == transaction.type_ and transaction.message and isinstance(transaction.message.message, str):
38
- transaction.message.message = transaction.message.message.encode('utf8')
39
-
40
36
  return transaction
41
37
 
42
38
  @staticmethod
@@ -66,9 +62,13 @@ class TransactionFactory:
66
62
  def attach_signature(transaction, signature):
67
63
  """Attaches a signature to a transaction."""
68
64
  transaction.signature = nc.Signature(signature.bytes)
65
+ return TransactionFactory.to_json(transaction)
69
66
 
67
+ @staticmethod
68
+ def to_json(transaction):
69
+ """Generates a JSON representation of transaction that can be sent to a node."""
70
70
  transaction_hex = hexlify(TransactionFactory.to_non_verifiable_transaction(transaction).serialize()).decode('utf8').upper()
71
- signature_hex = str(signature)
71
+ signature_hex = str(transaction.signature)
72
72
  json_payload = f'{{"data":"{transaction_hex}", "signature":"{signature_hex}"}}'
73
73
  return json_payload
74
74
 
@@ -0,0 +1,7 @@
1
+ from symbolchain.sc import Cosignature
2
+
3
+
4
+ def calculate_transaction_fee(transaction, fee_multiplier, cosignature_count=0):
5
+ """Calculates the minimum required transaction fee for a transaction."""
6
+
7
+ return transaction.size * fee_multiplier + Cosignature().size * cosignature_count
@@ -19,6 +19,9 @@ def generate_mosaic_id(owner_address, nonce):
19
19
 
20
20
  def generate_namespace_id(name, parent_namespace_id=0):
21
21
  """Generates a namespace id from a name and an optional parent namespace id."""
22
+ if '.' in name:
23
+ raise ValueError(f'\'name\' cannot contain \'.\'; if {name} is a namepace path, consider using generate_namespace_path')
24
+
22
25
  hasher = hashlib.sha3_256()
23
26
  hasher.update(parent_namespace_id.to_bytes(8, 'little'))
24
27
  hasher.update(name.encode('utf8'))
@@ -33,6 +36,11 @@ def generate_mosaic_alias_id(fully_qualified_name):
33
36
  return generate_namespace_path(fully_qualified_name)[-1]
34
37
 
35
38
 
39
+ def is_mosaic_alias(mosaic_id):
40
+ """Determines if mosaic_id is an alias."""
41
+ return 0 != (mosaic_id & NAMESPACE_FLAG)
42
+
43
+
36
44
  def is_valid_namespace_name(name):
37
45
  """Returns true if a name is a valid namespace name."""
38
46
  def is_alphanum(character):
@@ -41,11 +41,16 @@ class Address(ByteArray):
41
41
  def to_namespace_id(self):
42
42
  """Attempts to convert this address into a namespace id."""
43
43
 
44
- if not self.bytes[0] & 0x01:
44
+ if not self.is_alias():
45
45
  return None
46
46
 
47
47
  return NamespaceId(int.from_bytes(self.bytes[1:9], 'little'))
48
48
 
49
+ def is_alias(self):
50
+ """Determines if this address is an alias."""
51
+
52
+ return 0 != (self.bytes[0] & 0x01)
53
+
49
54
  def __str__(self):
50
55
  return base64.b32encode(self.bytes + bytes(0)).decode('utf8')[0:-1]
51
56
 
@@ -0,0 +1,10 @@
1
+ import hashlib
2
+
3
+
4
+ def mosaic_restriction_generate_key(seed):
5
+ """Generates a mosaic restriction key from a string."""
6
+
7
+ hash_result = hashlib.sha3_256(seed.encode('utf8')).digest()
8
+ key_bytes = bytearray(hash_result[:8])
9
+
10
+ return int.from_bytes(key_bytes, 'little')
@@ -69,7 +69,11 @@ class TransactionFactory:
69
69
  def attach_signature(transaction, signature):
70
70
  """Attaches a signature to a transaction."""
71
71
  transaction.signature = sc.Signature(signature.bytes)
72
+ return TransactionFactory.to_json(transaction)
72
73
 
74
+ @staticmethod
75
+ def to_json(transaction):
76
+ """Generates a JSON representation of transaction that can be sent to a node."""
73
77
  transaction_buffer = transaction.serialize()
74
78
  hex_payload = hexlify(transaction_buffer).decode('utf8').upper()
75
79
  json_payload = f'{{"payload": "{hex_payload}"}}'