symbol-sdk-python 3.3.1__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.1
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
@@ -17,7 +17,7 @@ Classifier: Programming Language :: Python :: 3.11
17
17
  Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: 3.13
19
19
  Classifier: Programming Language :: Python :: 3.14
20
- Requires-Dist: cryptography (>=46.0.1,<46.1.0)
20
+ Requires-Dist: cryptography (>=49.0.0,<49.1.0)
21
21
  Requires-Dist: mnemonic (>=0.20,<1.0)
22
22
  Requires-Dist: pillow (>=12.2.0,<12.3.0)
23
23
  Requires-Dist: pynacl (>=1.6.0,<1.7.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,14 +31,16 @@ 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=m20mHdIhX7b6uxwO__UuyuSL_onw8B42_49dSMgvr4M,4356
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
43
+ symbolchain/symbol/FeeCalculator.py,sha256=BmWjyGZeAJEW6LJpT2l8aXQcB4KongfKtzQ6CvO1k-0,280
42
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
@@ -50,6 +52,6 @@ symbolchain/symbol/SharedKey.py,sha256=Bg1qYW_iMNYKtokep5S1m9b7GfywM5OGaCrVUdt01
50
52
  symbolchain/symbol/TransactionFactory.py,sha256=YkQC2yBI5pSuSTc7-Aivh9kIKt32-Ho9XYWbbye2syw,4131
51
53
  symbolchain/symbol/VotingKeysGenerator.py,sha256=tuJ6yl2CqqQ9uupAw3z-GJIW20WOsblcLQb2_fPUCDE,1686
52
54
  symbolchain/symbol/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
- symbol_sdk_python-3.3.1.dist-info/METADATA,sha256=BcOmBmowQbPXsPrXXAIo8P0xPIgBZBXCSKd_ISwu18w,5707
54
- symbol_sdk_python-3.3.1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
55
- symbol_sdk_python-3.3.1.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
@@ -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