nemtus-symbol-sdk 3.3.2__tar.gz
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.
- nemtus_symbol_sdk-3.3.2/LICENSE +27 -0
- nemtus_symbol_sdk-3.3.2/PKG-INFO +145 -0
- nemtus_symbol_sdk-3.3.2/README.md +113 -0
- nemtus_symbol_sdk-3.3.2/pyproject.toml +29 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/AccountDescriptorRepository.py +59 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/ArrayHelpers.py +116 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/BaseValue.py +55 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/Bip32.py +56 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/BlockchainSettings.py +21 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/BufferReader.py +34 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/BufferWriter.py +26 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/ByteArray.py +43 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/Cipher.py +66 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/CodeWordsEncoder.py +47 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/CryptoTypes.py +83 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/DiceMnemonicGenerator.py +56 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/Network.py +100 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/NetworkTimestamp.py +53 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/NodeDescriptorRepository.py +27 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/Ordered.py +20 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/PrivateKeyStorage.py +41 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/QrSignatureStorage.py +37 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/QrStorage.py +42 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/RuleBasedTransactionFactory.py +177 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/SharedKey.py +16 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/TransactionDescriptorProcessor.py +50 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/Transforms.py +9 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/__init__.py +0 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/external/__init__.py +0 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/external/ed25519.py +313 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/facade/BatchOperations.py +66 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/facade/NemFacade.py +117 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/facade/SymbolFacade.py +191 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/facade/__init__.py +0 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/impl/CipherHelpers.py +57 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/impl/__init__.py +0 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/nc/__init__.py +3842 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/nem/FeeCalculator.py +94 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/nem/KeyPair.py +137 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/nem/MessageEncoder.py +78 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/nem/Network.py +58 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/nem/SharedKey.py +26 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/nem/TransactionFactory.py +110 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/nem/__init__.py +0 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/ripemd160.py +17 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/sc/__init__.py +9989 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/symbol/FeeCalculator.py +7 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/symbol/IdGenerator.py +63 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/symbol/KeyPair.py +49 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/symbol/Merkle.py +266 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/symbol/MessageEncoder.py +103 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/symbol/Metadata.py +32 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/symbol/Network.py +98 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/symbol/Restriction.py +10 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/symbol/SharedKey.py +9 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/symbol/TransactionFactory.py +109 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/symbol/VotingKeysGenerator.py +38 -0
- nemtus_symbol_sdk-3.3.2/symbolchain/symbol/__init__.py +0 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
This package is a redistribution of `symbol-sdk-python` (import module
|
|
4
|
+
`symbolchain`) from the upstream project symbol/symbol
|
|
5
|
+
(https://github.com/symbol/symbol), published by its authors — identified in the
|
|
6
|
+
upstream package metadata as "Symbol Contributors <contributors@symbol.dev>" —
|
|
7
|
+
under the MIT License. The nemtus mirror asserts no copyright over this software
|
|
8
|
+
and changes nothing but the published PyPI distribution name (to
|
|
9
|
+
`nemtus-symbol-sdk`). The authoritative copyright and license are upstream's.
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nemtus-symbol-sdk
|
|
3
|
+
Version: 3.3.2
|
|
4
|
+
Summary: Symbol Python SDK (nemtus mirror of upstream symbol-sdk-python; import module: symbolchain)
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: symbol,sdk,Symbol SDK
|
|
8
|
+
Author: Symbol Contributors
|
|
9
|
+
Author-email: contributors@symbol.dev
|
|
10
|
+
Maintainer: Symbol Contributors
|
|
11
|
+
Maintainer-email: contributors@symbol.dev
|
|
12
|
+
Requires-Python: >=3.10,<4.0
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Requires-Dist: cryptography (>=49.0.0,<49.1.0)
|
|
21
|
+
Requires-Dist: mnemonic (>=0.20,<1.0)
|
|
22
|
+
Requires-Dist: pillow (>=12.2.0,<12.3.0)
|
|
23
|
+
Requires-Dist: pynacl (>=1.6.0,<1.7.0)
|
|
24
|
+
Requires-Dist: pyyaml (>=6.0.1,<6.1.0)
|
|
25
|
+
Requires-Dist: pyzbar (>=0.1.9,<0.2.0)
|
|
26
|
+
Requires-Dist: qrcode (>=8.0,<9.0)
|
|
27
|
+
Requires-Dist: ripemd-hash (>=1.0.1,<1.1.0)
|
|
28
|
+
Requires-Dist: safe-pysha3 (>=1.0.4,<1.1.0)
|
|
29
|
+
Project-URL: Repository, https://github.com/nemtus/symbol/tree/dev/sdk/python
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
<!-- nemtus-mirror-notice -->
|
|
33
|
+
> **Note (nemtus mirror):** This package is a content mirror of the **Python** SDK
|
|
34
|
+
> from [`symbol/symbol`](https://github.com/symbol/symbol) (`sdk/python`, upstream
|
|
35
|
+
> PyPI name `symbol-sdk-python`). nemtus republishes it on PyPI as
|
|
36
|
+
> [`nemtus-symbol-sdk`](https://pypi.org/project/nemtus-symbol-sdk/); the only
|
|
37
|
+
> change from upstream is the published distribution name. The import module name
|
|
38
|
+
> is unchanged — you still `import symbolchain`. This is distinct from
|
|
39
|
+
> `@nemtus/symbol-sdk` on npm, which mirrors the JavaScript SDK. For the canonical
|
|
40
|
+
> project, see [symbol/symbol](https://github.com/symbol/symbol).
|
|
41
|
+
<!-- /nemtus-mirror-notice -->
|
|
42
|
+
|
|
43
|
+
# Symbol-SDK
|
|
44
|
+
|
|
45
|
+
[![lint][sdk-python-lint]][sdk-python-job] [![test][sdk-python-test]][sdk-python-job] [![vectors][sdk-python-vectors]][sdk-python-job] [![][sdk-python-cov]][sdk-python-cov-link] [![][sdk-python-package]][sdk-python-package-link]
|
|
46
|
+
|
|
47
|
+
[sdk-python-job]: https://jenkins.symbolsyndicate.us/blue/organizations/jenkins/Symbol%2Fgenerated%2Fsymbol%2Fpython/activity?branch=dev
|
|
48
|
+
[sdk-python-lint]: https://jenkins.symbolsyndicate.us/buildStatus/icon?job=Symbol%2Fgenerated%2Fsymbol%2Fpython%2Fdev%2F&config=sdk-python-lint
|
|
49
|
+
[sdk-python-build]: https://jenkins.symbolsyndicate.us/buildStatus/icon?job=Symbol%2Fgenerated%2Fsymbol%2Fpython%2Fdev%2F&config=sdk-python-build
|
|
50
|
+
[sdk-python-test]: https://jenkins.symbolsyndicate.us/buildStatus/icon?job=Symbol%2Fgenerated%2Fsymbol%2Fpython%2Fdev%2F&config=sdk-python-test
|
|
51
|
+
[sdk-python-examples]: https://jenkins.symbolsyndicate.us/buildStatus/icon?job=Symbol%2Fgenerated%2Fsymbol%2Fpython%2Fdev%2F&config=sdk-python-examples
|
|
52
|
+
[sdk-python-vectors]: https://jenkins.symbolsyndicate.us/buildStatus/icon?job=Symbol%2Fgenerated%2Fsymbol%2Fpython%2Fdev%2F&config=sdk-python-vectors
|
|
53
|
+
[sdk-python-cov]: https://codecov.io/gh/symbol/symbol/branch/dev/graph/badge.svg?token=SSYYBMK0M7&flag=sdk-python
|
|
54
|
+
[sdk-python-cov-link]: https://codecov.io/gh/symbol/symbol/tree/dev/sdk/python
|
|
55
|
+
[sdk-python-package]: https://img.shields.io/pypi/v/symbol-sdk-python
|
|
56
|
+
[sdk-python-package-link]: https://pypi.org/project/symbol-sdk-python
|
|
57
|
+
|
|
58
|
+
Python SDK for interacting with the Symbol and NEM blockchains.
|
|
59
|
+
|
|
60
|
+
Most common functionality is grouped under facades so that the same programming paradigm can be used for interacting with both Symbol and NEM.
|
|
61
|
+
|
|
62
|
+
## Sending a Transaction
|
|
63
|
+
|
|
64
|
+
To send a transaction, first create a facade for the desired network:
|
|
65
|
+
|
|
66
|
+
_Symbol_
|
|
67
|
+
```python
|
|
68
|
+
from symbolchain.CryptoTypes import PrivateKey
|
|
69
|
+
from symbolchain.facade.SymbolFacade import SymbolFacade
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
facade = SymbolFacade('testnet')
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
_NEM_
|
|
76
|
+
```python
|
|
77
|
+
from symbolchain.CryptoTypes import PrivateKey
|
|
78
|
+
from symbolchain.facade.NemFacade import NemFacade
|
|
79
|
+
|
|
80
|
+
facade = NemFacade('testnet')
|
|
81
|
+
````
|
|
82
|
+
|
|
83
|
+
Second, describe the transaction using a Python dictionary. For example, a transfer transaction can be described as follows:
|
|
84
|
+
|
|
85
|
+
_Symbol_
|
|
86
|
+
```python
|
|
87
|
+
transaction = facade.transaction_factory.create({
|
|
88
|
+
'type': 'transfer_transaction_v1',
|
|
89
|
+
'signer_public_key': '87DA603E7BE5656C45692D5FC7F6D0EF8F24BB7A5C10ED5FDA8C5CFBC49FCBC8',
|
|
90
|
+
'fee': 1000000,
|
|
91
|
+
'deadline': 41998024783,
|
|
92
|
+
'recipient_address': 'TCHBDENCLKEBILBPWP3JPB2XNY64OE7PYHHE32I',
|
|
93
|
+
'mosaics': [
|
|
94
|
+
{'mosaic_id': 0x7CDF3B117A3C40CC, 'amount': 1000000}
|
|
95
|
+
]
|
|
96
|
+
})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
_NEM_
|
|
100
|
+
```python
|
|
101
|
+
transaction = facade.transaction_factory.create({
|
|
102
|
+
'type': 'transfer_transaction_v1',
|
|
103
|
+
'signer_public_key': 'A59277D56E9F4FA46854F5EFAAA253B09F8AE69A473565E01FD9E6A738E4AB74',
|
|
104
|
+
'fee': 0x186A0,
|
|
105
|
+
'timestamp': 191205516,
|
|
106
|
+
'deadline': 191291916,
|
|
107
|
+
'recipient_address': 'TALICE5VF6J5FYMTCB7A3QG6OIRDRUXDWJGFVXNW',
|
|
108
|
+
'amount': 5100000
|
|
109
|
+
})
|
|
110
|
+
````
|
|
111
|
+
|
|
112
|
+
Third, sign the transaction and attach the signature:
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
private_key = PrivateKey('EDB671EB741BD676969D8A035271D1EE5E75DF33278083D877F23615EB839FEC')
|
|
117
|
+
signature = facade.sign_transaction(facade.KeyPair(private_key), transaction)
|
|
118
|
+
|
|
119
|
+
json_payload = facade.transaction_factory.attach_signature(transaction, signature)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Finally, send the payload to the desired network using the specified node endpoint:
|
|
123
|
+
|
|
124
|
+
_Symbol_: PUT `/transactions`
|
|
125
|
+
<br>
|
|
126
|
+
_NEM_: POST `/transaction/announce`
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
## NEM Cheat Sheet
|
|
130
|
+
|
|
131
|
+
In order to simplify the learning curve for NEM and Symbol usage, the SDK uses Symbol terminology for shared Symbol and NEM concepts.
|
|
132
|
+
Where appropriate, NEM terminology is replaced with Symbol terminology, including the names of many of the NEM transactions.
|
|
133
|
+
The mapping of NEM transactions to SDK descriptors can be found in the following table:
|
|
134
|
+
|
|
135
|
+
| NEM name (used in docs) | SDK descriptor name|
|
|
136
|
+
|--- |--- |
|
|
137
|
+
| ImportanceTransfer transaction | `account_key_link_transaction_v1` |
|
|
138
|
+
| MosaicDefinitionCreation transaction | `mosaic_definition_transaction_v1` |
|
|
139
|
+
| MosaicSupplyChange transaction | `mosaic_supply_change_transaction_v1` |
|
|
140
|
+
| MultisigAggregateModification transaction | `multisig_account_modification_transaction_v1`<br>`multisig_account_modification_transaction_v2` |
|
|
141
|
+
| MultisigSignature transaction or Cosignature transaction | `cosignature_v1` |
|
|
142
|
+
| Multisig transaction | `multisig_transaction_v1` |
|
|
143
|
+
| ProvisionNamespace transaction | `namespace_registration_transaction_v1` |
|
|
144
|
+
| Transfer transaction | `transfer_transaction_v1`<br>`transfer_transaction_v2` |
|
|
145
|
+
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
<!-- nemtus-mirror-notice -->
|
|
2
|
+
> **Note (nemtus mirror):** This package is a content mirror of the **Python** SDK
|
|
3
|
+
> from [`symbol/symbol`](https://github.com/symbol/symbol) (`sdk/python`, upstream
|
|
4
|
+
> PyPI name `symbol-sdk-python`). nemtus republishes it on PyPI as
|
|
5
|
+
> [`nemtus-symbol-sdk`](https://pypi.org/project/nemtus-symbol-sdk/); the only
|
|
6
|
+
> change from upstream is the published distribution name. The import module name
|
|
7
|
+
> is unchanged — you still `import symbolchain`. This is distinct from
|
|
8
|
+
> `@nemtus/symbol-sdk` on npm, which mirrors the JavaScript SDK. For the canonical
|
|
9
|
+
> project, see [symbol/symbol](https://github.com/symbol/symbol).
|
|
10
|
+
<!-- /nemtus-mirror-notice -->
|
|
11
|
+
|
|
12
|
+
# Symbol-SDK
|
|
13
|
+
|
|
14
|
+
[![lint][sdk-python-lint]][sdk-python-job] [![test][sdk-python-test]][sdk-python-job] [![vectors][sdk-python-vectors]][sdk-python-job] [![][sdk-python-cov]][sdk-python-cov-link] [![][sdk-python-package]][sdk-python-package-link]
|
|
15
|
+
|
|
16
|
+
[sdk-python-job]: https://jenkins.symbolsyndicate.us/blue/organizations/jenkins/Symbol%2Fgenerated%2Fsymbol%2Fpython/activity?branch=dev
|
|
17
|
+
[sdk-python-lint]: https://jenkins.symbolsyndicate.us/buildStatus/icon?job=Symbol%2Fgenerated%2Fsymbol%2Fpython%2Fdev%2F&config=sdk-python-lint
|
|
18
|
+
[sdk-python-build]: https://jenkins.symbolsyndicate.us/buildStatus/icon?job=Symbol%2Fgenerated%2Fsymbol%2Fpython%2Fdev%2F&config=sdk-python-build
|
|
19
|
+
[sdk-python-test]: https://jenkins.symbolsyndicate.us/buildStatus/icon?job=Symbol%2Fgenerated%2Fsymbol%2Fpython%2Fdev%2F&config=sdk-python-test
|
|
20
|
+
[sdk-python-examples]: https://jenkins.symbolsyndicate.us/buildStatus/icon?job=Symbol%2Fgenerated%2Fsymbol%2Fpython%2Fdev%2F&config=sdk-python-examples
|
|
21
|
+
[sdk-python-vectors]: https://jenkins.symbolsyndicate.us/buildStatus/icon?job=Symbol%2Fgenerated%2Fsymbol%2Fpython%2Fdev%2F&config=sdk-python-vectors
|
|
22
|
+
[sdk-python-cov]: https://codecov.io/gh/symbol/symbol/branch/dev/graph/badge.svg?token=SSYYBMK0M7&flag=sdk-python
|
|
23
|
+
[sdk-python-cov-link]: https://codecov.io/gh/symbol/symbol/tree/dev/sdk/python
|
|
24
|
+
[sdk-python-package]: https://img.shields.io/pypi/v/symbol-sdk-python
|
|
25
|
+
[sdk-python-package-link]: https://pypi.org/project/symbol-sdk-python
|
|
26
|
+
|
|
27
|
+
Python SDK for interacting with the Symbol and NEM blockchains.
|
|
28
|
+
|
|
29
|
+
Most common functionality is grouped under facades so that the same programming paradigm can be used for interacting with both Symbol and NEM.
|
|
30
|
+
|
|
31
|
+
## Sending a Transaction
|
|
32
|
+
|
|
33
|
+
To send a transaction, first create a facade for the desired network:
|
|
34
|
+
|
|
35
|
+
_Symbol_
|
|
36
|
+
```python
|
|
37
|
+
from symbolchain.CryptoTypes import PrivateKey
|
|
38
|
+
from symbolchain.facade.SymbolFacade import SymbolFacade
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
facade = SymbolFacade('testnet')
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
_NEM_
|
|
45
|
+
```python
|
|
46
|
+
from symbolchain.CryptoTypes import PrivateKey
|
|
47
|
+
from symbolchain.facade.NemFacade import NemFacade
|
|
48
|
+
|
|
49
|
+
facade = NemFacade('testnet')
|
|
50
|
+
````
|
|
51
|
+
|
|
52
|
+
Second, describe the transaction using a Python dictionary. For example, a transfer transaction can be described as follows:
|
|
53
|
+
|
|
54
|
+
_Symbol_
|
|
55
|
+
```python
|
|
56
|
+
transaction = facade.transaction_factory.create({
|
|
57
|
+
'type': 'transfer_transaction_v1',
|
|
58
|
+
'signer_public_key': '87DA603E7BE5656C45692D5FC7F6D0EF8F24BB7A5C10ED5FDA8C5CFBC49FCBC8',
|
|
59
|
+
'fee': 1000000,
|
|
60
|
+
'deadline': 41998024783,
|
|
61
|
+
'recipient_address': 'TCHBDENCLKEBILBPWP3JPB2XNY64OE7PYHHE32I',
|
|
62
|
+
'mosaics': [
|
|
63
|
+
{'mosaic_id': 0x7CDF3B117A3C40CC, 'amount': 1000000}
|
|
64
|
+
]
|
|
65
|
+
})
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
_NEM_
|
|
69
|
+
```python
|
|
70
|
+
transaction = facade.transaction_factory.create({
|
|
71
|
+
'type': 'transfer_transaction_v1',
|
|
72
|
+
'signer_public_key': 'A59277D56E9F4FA46854F5EFAAA253B09F8AE69A473565E01FD9E6A738E4AB74',
|
|
73
|
+
'fee': 0x186A0,
|
|
74
|
+
'timestamp': 191205516,
|
|
75
|
+
'deadline': 191291916,
|
|
76
|
+
'recipient_address': 'TALICE5VF6J5FYMTCB7A3QG6OIRDRUXDWJGFVXNW',
|
|
77
|
+
'amount': 5100000
|
|
78
|
+
})
|
|
79
|
+
````
|
|
80
|
+
|
|
81
|
+
Third, sign the transaction and attach the signature:
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
private_key = PrivateKey('EDB671EB741BD676969D8A035271D1EE5E75DF33278083D877F23615EB839FEC')
|
|
86
|
+
signature = facade.sign_transaction(facade.KeyPair(private_key), transaction)
|
|
87
|
+
|
|
88
|
+
json_payload = facade.transaction_factory.attach_signature(transaction, signature)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Finally, send the payload to the desired network using the specified node endpoint:
|
|
92
|
+
|
|
93
|
+
_Symbol_: PUT `/transactions`
|
|
94
|
+
<br>
|
|
95
|
+
_NEM_: POST `/transaction/announce`
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
## NEM Cheat Sheet
|
|
99
|
+
|
|
100
|
+
In order to simplify the learning curve for NEM and Symbol usage, the SDK uses Symbol terminology for shared Symbol and NEM concepts.
|
|
101
|
+
Where appropriate, NEM terminology is replaced with Symbol terminology, including the names of many of the NEM transactions.
|
|
102
|
+
The mapping of NEM transactions to SDK descriptors can be found in the following table:
|
|
103
|
+
|
|
104
|
+
| NEM name (used in docs) | SDK descriptor name|
|
|
105
|
+
|--- |--- |
|
|
106
|
+
| ImportanceTransfer transaction | `account_key_link_transaction_v1` |
|
|
107
|
+
| MosaicDefinitionCreation transaction | `mosaic_definition_transaction_v1` |
|
|
108
|
+
| MosaicSupplyChange transaction | `mosaic_supply_change_transaction_v1` |
|
|
109
|
+
| MultisigAggregateModification transaction | `multisig_account_modification_transaction_v1`<br>`multisig_account_modification_transaction_v2` |
|
|
110
|
+
| MultisigSignature transaction or Cosignature transaction | `cosignature_v1` |
|
|
111
|
+
| Multisig transaction | `multisig_transaction_v1` |
|
|
112
|
+
| ProvisionNamespace transaction | `namespace_registration_transaction_v1` |
|
|
113
|
+
| Transfer transaction | `transfer_transaction_v1`<br>`transfer_transaction_v2` |
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = 'nemtus-symbol-sdk'
|
|
3
|
+
version = '3.3.2'
|
|
4
|
+
description = 'Symbol Python SDK (nemtus mirror of upstream symbol-sdk-python; import module: symbolchain)'
|
|
5
|
+
authors = ['Symbol Contributors <contributors@symbol.dev>']
|
|
6
|
+
maintainers = ['Symbol Contributors <contributors@symbol.dev>']
|
|
7
|
+
license = 'MIT'
|
|
8
|
+
|
|
9
|
+
readme = 'README.md'
|
|
10
|
+
|
|
11
|
+
packages = [{ include = 'symbolchain' }]
|
|
12
|
+
|
|
13
|
+
repository = 'https://github.com/nemtus/symbol/tree/dev/sdk/python'
|
|
14
|
+
|
|
15
|
+
keywords = ['symbol', 'sdk', 'Symbol SDK']
|
|
16
|
+
|
|
17
|
+
classifiers = ['Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', 'Programming Language :: Python :: 3.14']
|
|
18
|
+
|
|
19
|
+
[tool.poetry.dependencies]
|
|
20
|
+
python = "^3.10"
|
|
21
|
+
cryptography = ">=49.0.0,<49.1.0"
|
|
22
|
+
mnemonic = ">=0.20,<1.0"
|
|
23
|
+
pillow = ">=12.2.0,<12.3.0"
|
|
24
|
+
pynacl = ">=1.6.0,<1.7.0"
|
|
25
|
+
pyyaml = ">=6.0.1,<6.1.0"
|
|
26
|
+
pyzbar = ">=0.1.9,<0.2.0"
|
|
27
|
+
qrcode = ">=8.0,<9.0"
|
|
28
|
+
ripemd-hash = ">=1.0.1,<1.1.0"
|
|
29
|
+
safe-pysha3 = ">=1.0.4,<1.1.0"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import yaml
|
|
2
|
+
|
|
3
|
+
from .CryptoTypes import PublicKey
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AccountDescriptor:
|
|
7
|
+
"""Represents an account."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, descriptor_yaml):
|
|
10
|
+
"""Creates a descriptor from a yaml container."""
|
|
11
|
+
self.public_key = descriptor_yaml.get('public_key')
|
|
12
|
+
if self.public_key:
|
|
13
|
+
self.public_key = PublicKey(self.public_key)
|
|
14
|
+
|
|
15
|
+
self.address = descriptor_yaml.get('address')
|
|
16
|
+
self.name = descriptor_yaml.get('name')
|
|
17
|
+
self.roles = descriptor_yaml.get('roles') or []
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AccountDescriptorRepository:
|
|
21
|
+
"""Loads read-only account descriptors from YAML."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, yaml_input):
|
|
24
|
+
"""Loads account descriptors from the specified input."""
|
|
25
|
+
descriptors_yaml = yaml_input if isinstance(yaml_input, list) else yaml.load(yaml_input, Loader=yaml.SafeLoader)
|
|
26
|
+
self.descriptors = [AccountDescriptor(descriptor_yaml) for descriptor_yaml in descriptors_yaml]
|
|
27
|
+
|
|
28
|
+
def try_find_by_name(self, name):
|
|
29
|
+
"""Finds the account descriptor with a matching name or None if no matching descriptors are found."""
|
|
30
|
+
return next((descriptor for descriptor in self.descriptors if name == descriptor.name), None)
|
|
31
|
+
|
|
32
|
+
def find_by_public_key(self, public_key):
|
|
33
|
+
"""Finds the account descriptor with a matching public key."""
|
|
34
|
+
return next(descriptor for descriptor in self.descriptors if descriptor.public_key and public_key == descriptor.public_key)
|
|
35
|
+
|
|
36
|
+
def find_by_address(self, address):
|
|
37
|
+
"""Finds the account descriptor with a matching address."""
|
|
38
|
+
return next(descriptor for descriptor in self.descriptors if descriptor.address and str(address) == descriptor.address)
|
|
39
|
+
|
|
40
|
+
def find_all_by_role(self, role):
|
|
41
|
+
"""Finds all account descriptors with a matching role."""
|
|
42
|
+
return [descriptor for descriptor in self.descriptors if not role or role in descriptor.roles]
|
|
43
|
+
|
|
44
|
+
def _lookup_account_descriptor_field(self, value, property_name, target_class):
|
|
45
|
+
account_descriptor = self.try_find_by_name(value)
|
|
46
|
+
if account_descriptor and hasattr(account_descriptor, property_name):
|
|
47
|
+
return target_class(getattr(account_descriptor, property_name))
|
|
48
|
+
|
|
49
|
+
return target_class(value)
|
|
50
|
+
|
|
51
|
+
def _bind_lookup_account_descriptor_field(self, property_name, target_class):
|
|
52
|
+
return lambda value: self._lookup_account_descriptor_field(value, property_name, target_class)
|
|
53
|
+
|
|
54
|
+
def to_type_parsing_rules_map(self, type_to_property_mapping):
|
|
55
|
+
"""Builds a type to parsing rule map."""
|
|
56
|
+
return {
|
|
57
|
+
target_class: self._bind_lookup_account_descriptor_field(property_name, target_class)
|
|
58
|
+
for target_class, property_name in type_to_property_mapping.items()
|
|
59
|
+
}
|
|
@@ -0,0 +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
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from .Ordered import Ordered
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BaseValue(Ordered):
|
|
5
|
+
"""Represents a base int."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, size, value, tag=None, signed=False):
|
|
8
|
+
"""Creates a base value."""
|
|
9
|
+
self.size = size
|
|
10
|
+
self.value = value
|
|
11
|
+
self.__tag = (tag, signed)
|
|
12
|
+
|
|
13
|
+
# check bounds
|
|
14
|
+
bit_size = self.size * 8
|
|
15
|
+
if signed:
|
|
16
|
+
upper_bound = (1 << (bit_size - 1)) - 1
|
|
17
|
+
lower_bound = -upper_bound - 1
|
|
18
|
+
else:
|
|
19
|
+
upper_bound = (1 << bit_size) - 1
|
|
20
|
+
lower_bound = 0
|
|
21
|
+
|
|
22
|
+
if self.value < lower_bound or self.value > upper_bound:
|
|
23
|
+
signed_description = 'signed' if signed else 'unsigned'
|
|
24
|
+
value_range_message = f'{value} must be in range [{lower_bound}, {upper_bound}]'
|
|
25
|
+
raise ValueError(f'{value_range_message} for {self.size} bytes ({signed_description})')
|
|
26
|
+
|
|
27
|
+
def _cmp(self, other, operation):
|
|
28
|
+
if not isinstance(other, BaseValue):
|
|
29
|
+
return NotImplemented
|
|
30
|
+
|
|
31
|
+
# pylint: disable=protected-access
|
|
32
|
+
return operation(self.value, other.value) and self.__tag == other.__tag
|
|
33
|
+
|
|
34
|
+
def __eq__(self, other):
|
|
35
|
+
# pylint: disable=protected-access
|
|
36
|
+
return isinstance(other, BaseValue) and self.value == other.value and self.__tag == other.__tag
|
|
37
|
+
|
|
38
|
+
def __ne__(self, other):
|
|
39
|
+
return not self == other
|
|
40
|
+
|
|
41
|
+
def __hash__(self):
|
|
42
|
+
return hash((self.value, self.__tag))
|
|
43
|
+
|
|
44
|
+
def __str__(self):
|
|
45
|
+
if not self.__tag[1] or self.value >= 0:
|
|
46
|
+
unsigned_value = self.value
|
|
47
|
+
else:
|
|
48
|
+
upper_bound_plus_one = 1 << (self.size * 8)
|
|
49
|
+
unsigned_value = self.value + upper_bound_plus_one
|
|
50
|
+
|
|
51
|
+
return f'0x{unsigned_value:0{self.size * 2}X}'
|
|
52
|
+
|
|
53
|
+
def to_json(self):
|
|
54
|
+
"""Returns representation of this object that can be stored in JSON."""
|
|
55
|
+
return str(self.value) if 8 <= self.size else self.value
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import hmac
|
|
3
|
+
import secrets
|
|
4
|
+
|
|
5
|
+
from mnemonic import Mnemonic
|
|
6
|
+
|
|
7
|
+
from .BufferWriter import BufferWriter
|
|
8
|
+
from .CryptoTypes import PrivateKey
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Bip32Node:
|
|
12
|
+
"""Representation of a BIP32 node."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, hmac_key, data):
|
|
15
|
+
"""Creates a BIP32 node around a key and data."""
|
|
16
|
+
hmac_result = hmac.new(hmac_key, data, hashlib.sha512).digest()
|
|
17
|
+
|
|
18
|
+
self.private_key = PrivateKey(hmac_result[0:PrivateKey.SIZE])
|
|
19
|
+
self.chain_code = hmac_result[PrivateKey.SIZE:]
|
|
20
|
+
|
|
21
|
+
def derive_one(self, identifier):
|
|
22
|
+
"""Derives a direct child node with specified identifier."""
|
|
23
|
+
hmac_data_writer = BufferWriter('big')
|
|
24
|
+
hmac_data_writer.write_int(0, 1)
|
|
25
|
+
hmac_data_writer.write_bytes(self.private_key.bytes)
|
|
26
|
+
hmac_data_writer.write_int(0x80000000 | identifier, 4)
|
|
27
|
+
return Bip32Node(self.chain_code, hmac_data_writer.buffer)
|
|
28
|
+
|
|
29
|
+
def derive_path(self, path):
|
|
30
|
+
"""Derives a descendent node with specified path."""
|
|
31
|
+
next_node = self
|
|
32
|
+
for identifier in path:
|
|
33
|
+
next_node = next_node.derive_one(identifier)
|
|
34
|
+
|
|
35
|
+
return next_node
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Bip32:
|
|
39
|
+
"""Factory of BIP32 root nodes """
|
|
40
|
+
|
|
41
|
+
def __init__(self, curve_name='ed25519', mnemonic_language='english'):
|
|
42
|
+
"""Creates a BIP32 root node factory."""
|
|
43
|
+
self.root_hmac_key = (curve_name + ' seed').encode('utf8')
|
|
44
|
+
self.mnemonic_language = mnemonic_language
|
|
45
|
+
|
|
46
|
+
def from_seed(self, seed):
|
|
47
|
+
"""Creates a BIP32 root node from a seed."""
|
|
48
|
+
return Bip32Node(self.root_hmac_key, seed)
|
|
49
|
+
|
|
50
|
+
def from_mnemonic(self, mnemonic, password):
|
|
51
|
+
"""Creates a BIP32 root node from a BIP39 mnemonic and password."""
|
|
52
|
+
return self.from_seed(Mnemonic(self.mnemonic_language).to_seed(mnemonic, password))
|
|
53
|
+
|
|
54
|
+
def random(self, seed_length=32):
|
|
55
|
+
"""Creates a random BIP32 mnemonic."""
|
|
56
|
+
return Mnemonic(self.mnemonic_language).to_mnemonic(secrets.token_bytes(seed_length))
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import yaml
|
|
2
|
+
|
|
3
|
+
from .AccountDescriptorRepository import AccountDescriptorRepository
|
|
4
|
+
from .NodeDescriptorRepository import NodeDescriptorRepository
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BlockchainSettings:
|
|
8
|
+
"""Settings describing a blockchain."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, settings_dict):
|
|
11
|
+
"""Creates blockchain settings from a dictionary."""
|
|
12
|
+
self.blockchain = settings_dict['blockchain']
|
|
13
|
+
self.network = settings_dict['network']
|
|
14
|
+
self.nodes = NodeDescriptorRepository(settings_dict['nodes'])
|
|
15
|
+
self.accounts = AccountDescriptorRepository(settings_dict['accounts'])
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def load_from_yaml(yaml_input):
|
|
19
|
+
"""Loads settings from YAML."""
|
|
20
|
+
settings_yaml = yaml.load(yaml_input, Loader=yaml.SafeLoader)
|
|
21
|
+
return BlockchainSettings(settings_yaml)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from binascii import hexlify
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BufferReader:
|
|
5
|
+
"""Reads data from an in memory buffer."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, buffer, byte_order='little'):
|
|
8
|
+
"""Creates a reader with specified byte order."""
|
|
9
|
+
self.buffer = buffer
|
|
10
|
+
self.byte_order = byte_order
|
|
11
|
+
self.offset = 0
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def eof(self):
|
|
15
|
+
"""Returns true if the reader is at eof."""
|
|
16
|
+
return self.offset == len(self.buffer)
|
|
17
|
+
|
|
18
|
+
def read_int(self, count):
|
|
19
|
+
"""Reads an integer."""
|
|
20
|
+
return int.from_bytes(self.read_bytes(count), self.byte_order)
|
|
21
|
+
|
|
22
|
+
def read_string(self, count):
|
|
23
|
+
"""Reads a string."""
|
|
24
|
+
return self.read_bytes(count).decode('utf8')
|
|
25
|
+
|
|
26
|
+
def read_hex_string(self, count):
|
|
27
|
+
"""Reads a hex string."""
|
|
28
|
+
return hexlify(self.read_bytes(count)).decode('utf8').upper()
|
|
29
|
+
|
|
30
|
+
def read_bytes(self, count):
|
|
31
|
+
"""Reads bytes."""
|
|
32
|
+
buffer = self.buffer[self.offset:self.offset + count]
|
|
33
|
+
self.offset += count
|
|
34
|
+
return buffer
|