diff --git a/.clang-format b/.clang-format
new file mode 100644
index 00000000..daa53e37
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,26 @@
+---
+Language: Cpp
+BasedOnStyle: Google
+AlignOperands: true
+BreakStringLiterals: true
+ColumnLimit: 80
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+FixNamespaceComments: true
+IndentCaseLabels: true
+IndentWidth: 4
+MaxEmptyLinesToKeep: 1
+PointerAlignment: Left
+ReflowComments: true
+SortIncludes: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: true
+UseTab: Never
+---
+Language: Proto
+BasedOnStyle: LLVM
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index bf920b8a..e61f9d5d 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,12 +1,19 @@
{
"python.linting.pylintEnabled": false,
- "python.linting.pylamaEnabled": true,
+ "python.linting.pylamaEnabled": false,
"python.linting.enabled": true,
"files.associations": {
- "*.h": "c",
- ".clang-tidy": "yaml",
- "string_view": "c",
- "regex": "c"
+ "*.h": "c",
+ ".clang-tidy": "yaml",
+ "string_view": "c",
+ "regex": "c",
+ "system_error": "c",
+ "array": "c",
+ "functional": "c",
+ "tuple": "c",
+ "type_traits": "c",
+ "utility": "c"
},
"C_Cpp.dimInactiveRegions": false,
+ "python.linting.mypyEnabled": true,
}
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index ae3ac373..00000000
--- a/LICENSE
+++ /dev/null
@@ -1,201 +0,0 @@
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- nanopb notice, with the fields enclosed by brackets "{}"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright 2019 Hedera Hashgraph LLC
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
diff --git a/Makefile b/Makefile
index 390fe555..22e7c050 100755
--- a/Makefile
+++ b/Makefile
@@ -62,7 +62,7 @@ DFEFINES += PB_FIELD_32BIT=1
# vendor/printf
DEFINES += PRINTF_DISABLE_SUPPORT_FLOAT PRINTF_DISABLE_SUPPORT_EXPONENTIAL PRINTF_DISABLE_SUPPORT_PTRDIFF_T
-DEFINES += PRINTF_NTOA_BUFFER_SIZE=9U PRINTF_FTOA_BUFFER_SIZE=0
+DEFINES += PRINTF_FTOA_BUFFER_SIZE=0
# endif
# U2F
@@ -160,39 +160,24 @@ DEFINES += PB_NO_ERRMSG=1
SOURCE_FILES += $(NANOPB_CORE)
CFLAGS += "-I$(NANOPB_DIR)"
-# Build rule for proto files
-SOURCE_FILES += proto/BasicTypes.pb.c
-SOURCE_FILES += proto/Wrappers.pb.c
-SOURCE_FILES += proto/CryptoCreate.pb.c
-SOURCE_FILES += proto/Transfer.pb.c
-SOURCE_FILES += proto/TransactionBody.pb.c
-SOURCE_FILES += proto/TokenAssociate.pb.c
-SOURCE_FILES += proto/TokenMint.pb.c
-SOURCE_FILES += proto/TokenBurn.pb.c
+PB_FILES = $(wildcard proto/*.proto)
+C_PB_FILES = $(patsubst %.proto,%.pb.c,$(PB_FILES))
+PYTHON_PB_FILES = $(patsubst %.proto,%_pb2.py,$(PB_FILES))
-proto/BasicTypes.pb.c: proto/BasicTypes.proto
- $(PROTOC) $(PROTOC_OPTS) --nanopb_out=. proto/BasicTypes.proto
+# Build rule for C proto files
+SOURCE_FILES += $(C_PB_FILES)
+$(C_PB_FILES): %.pb.c: $(PB_FILES)
+ $(PROTOC) $(PROTOC_OPTS) --nanopb_out=. $*.proto
-proto/Wrappers.pb.c: proto/Wrappers.proto
- $(PROTOC) $(PROTOC_OPTS) --nanopb_out=. proto/Wrappers.proto
+# Build rule for Python proto files
+$(PYTHON_PB_FILES): %_pb2.py: $(PB_FILES)
+ $(PROTOC) $(PROTOC_OPTS) --python_out=. --mypy_out=. $*.proto
-proto/CryptoCreate.pb.c: proto/BasicTypes.proto
- $(PROTOC) $(PROTOC_OPTS) --nanopb_out=. proto/CryptoCreate.proto
-
-proto/Transfer.pb.c: proto/BasicTypes.proto
- $(PROTOC) $(PROTOC_OPTS) --nanopb_out=. proto/Transfer.proto
-
-proto/TransactionBody.pb.c: proto/BasicTypes.proto
- $(PROTOC) $(PROTOC_OPTS) --nanopb_out=. proto/TransactionBody.proto
-
-proto/TokenAssociate.pb.c: proto/BasicTypes.proto
- $(PROTOC) $(PROTOC_OPTS) --nanopb_out=. proto/TokenAssociate.proto
-
-proto/TokenMint.pb.c: proto/BasicTypes.proto
- $(PROTOC) $(PROTOC_OPTS) --nanopb_out=. proto/TokenMint.proto
-
-proto/TokenBurn.pb.c: proto/BasicTypes.proto
- $(PROTOC) $(PROTOC_OPTS) --nanopb_out=. proto/TokenBurn.proto
+.PHONY: python_pb clean_python_pb
+c_pb: $(C_PB_FILES)
+python_pb: $(PYTHON_PB_FILES)
+clean_python_pb:
+ rm -f $(PYTHON_PB_FILES)
# target to also clean generated proto c files
.SILENT : cleanall
diff --git a/README.md b/README.md
deleted file mode 100644
index 647ef3c9..00000000
--- a/README.md
+++ /dev/null
@@ -1,27 +0,0 @@
-# Hedera Ledger App
-
-Hedera™ Hashgraph BOLOS application for Ledger Nano S and Nano X.
-
-## Development
-
-### Prerequisite
-
-- Docker
-
-### Compile
-
-```
-docker run -v $PWD:/app --platform linux/amd64 -it \
- ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder:latest \
- make
-```
-
-### Check
-
-```
-docker run -v $PWD:/app --platform linux/amd64 -it \
- ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder:latest \
- scan-build --use-cc=clang -analyze-headers -enable-checker security \
- -enable-checker unix -enable-checker valist -o scan-build \
- --status-bugs make default
-```
diff --git a/proto/BasicTypes.proto b/proto/BasicTypes.proto
deleted file mode 100644
index a691136d..00000000
--- a/proto/BasicTypes.proto
+++ /dev/null
@@ -1,43 +0,0 @@
-syntax = "proto3";
-
-import "nanopb.proto";
-
-message HederaKey {
- oneof key {
- bytes ed25519 = 2 [(nanopb).max_size = 32];
- }
-}
-
-message HederaShardID {
- uint64 shardNum = 1;
-}
-
-message HederaRealmID {
- uint64 shardNum = 1;
- uint64 realmNum = 2;
-}
-
-message HederaAccountID {
- uint64 shardNum = 1;
- uint64 realmNum = 2;
- uint64 accountNum = 3;
-}
-
-message HederaTokenID {
- uint64 shardNum = 1;
- uint64 realmNum = 2;
- uint64 tokenNum = 3;
-}
-
-message HederaTimestamp {
- uint64 seconds = 1;
- uint32 nanos = 2;
-}
-
-message HederaDuration {
- uint64 seconds = 1;
-}
-
-message HederaTransactionID {
- HederaAccountID accountID = 2;
-}
diff --git a/proto/CryptoCreate.proto b/proto/CryptoCreate.proto
deleted file mode 100644
index da04ce64..00000000
--- a/proto/CryptoCreate.proto
+++ /dev/null
@@ -1,5 +0,0 @@
-syntax = "proto3";
-
-message HederaCryptoCreateTransactionBody {
- uint64 initialBalance = 2;
-}
diff --git a/proto/TokenAssociate.proto b/proto/TokenAssociate.proto
deleted file mode 100644
index 4bfad268..00000000
--- a/proto/TokenAssociate.proto
+++ /dev/null
@@ -1,10 +0,0 @@
-syntax = "proto3";
-
-import "nanopb.proto";
-import "proto/BasicTypes.proto";
-
-message HederaTokenAssociateTransactionBody {
- HederaAccountID account = 1;
-
- repeated HederaTokenID tokens = 2 [(nanopb).max_count = 1];
-}
diff --git a/proto/TokenBurn.proto b/proto/TokenBurn.proto
deleted file mode 100644
index 10f42aaf..00000000
--- a/proto/TokenBurn.proto
+++ /dev/null
@@ -1,14 +0,0 @@
-syntax = "proto3";
-
-import "proto/Wrappers.proto";
-import "proto/BasicTypes.proto";
-
-message HederaTokenBurnTransactionBody {
- HederaTokenID token = 1;
-
- uint64 amount = 2;
-
- // TODO(nft): repeated int64 serialNumbers = 3;
-
- UInt32Value expected_decimals = 4;
-}
diff --git a/proto/TokenMint.proto b/proto/TokenMint.proto
deleted file mode 100644
index 2638adee..00000000
--- a/proto/TokenMint.proto
+++ /dev/null
@@ -1,14 +0,0 @@
-syntax = "proto3";
-
-import "proto/Wrappers.proto";
-import "proto/BasicTypes.proto";
-
-message HederaTokenMintTransactionBody {
- HederaTokenID token = 1;
-
- uint64 amount = 2;
-
- // TODO(nft): repeated bytes metadata = 3;
-
- UInt32Value expected_decimals = 4;
-}
diff --git a/proto/TransactionBody.proto b/proto/TransactionBody.proto
deleted file mode 100644
index 3fa24cf2..00000000
--- a/proto/TransactionBody.proto
+++ /dev/null
@@ -1,23 +0,0 @@
-syntax = "proto3";
-
-import "nanopb.proto";
-import "proto/BasicTypes.proto";
-import "proto/CryptoCreate.proto";
-import "proto/Transfer.proto";
-import "proto/TokenAssociate.proto";
-import "proto/TokenMint.proto";
-import "proto/TokenBurn.proto";
-
-message HederaTransactionBody {
- HederaTransactionID transactionID = 1;
- uint64 transactionFee = 3;
- string memo = 6 [(nanopb).max_size = 100];
-
- oneof data {
- HederaCryptoCreateTransactionBody cryptoCreateAccount = 11;
- HederaCryptoTransferTransactionBody cryptoTransfer = 14;
- HederaTokenAssociateTransactionBody tokenAssociate = 40;
- HederaTokenMintTransactionBody tokenMint = 37;
- HederaTokenBurnTransactionBody tokenBurn = 38;
- }
-}
diff --git a/proto/Transfer.proto b/proto/Transfer.proto
deleted file mode 100644
index 783ae912..00000000
--- a/proto/Transfer.proto
+++ /dev/null
@@ -1,32 +0,0 @@
-syntax = "proto3";
-
-import "nanopb.proto";
-import "proto/Wrappers.proto";
-import "proto/BasicTypes.proto";
-
-message HederaAccountAmount {
- HederaAccountID accountID = 1;
- sint64 amount = 2;
-}
-
-message HederaTransferList {
- repeated HederaAccountAmount accountAmounts = 1 [(nanopb).max_count = 2];
-}
-
-message HederaTokenTransferList {
- HederaTokenID token = 1;
-
- repeated HederaAccountAmount transfers = 2 [(nanopb).max_count = 2];
-
- /**
- * If present, the number of decimals this fungible token type is expected to have. The transfer
- * will fail with UNEXPECTED_TOKEN_DECIMALS if the actual decimals differ.
- */
- UInt32Value expected_decimals = 4;
-}
-
-message HederaCryptoTransferTransactionBody {
- HederaTransferList transfers = 1;
-
- repeated HederaTokenTransferList tokenTransfers = 2 [(nanopb).max_count = 1];
-}
diff --git a/proto/Wrappers.proto b/proto/Wrappers.proto
deleted file mode 100644
index 0fe25353..00000000
--- a/proto/Wrappers.proto
+++ /dev/null
@@ -1,5 +0,0 @@
-syntax = "proto3";
-
-message UInt32Value {
- uint32 value = 1;
-}
diff --git a/proto/basic_types.proto b/proto/basic_types.proto
new file mode 100644
index 00000000..6aec15af
--- /dev/null
+++ b/proto/basic_types.proto
@@ -0,0 +1,594 @@
+syntax = "proto3";
+
+package Hedera;
+
+/*-
+ *
+ * Hedera Network Services Protobuf
+ *
+ * Copyright (C) 2018 - 2022 Hedera Hashgraph, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import "nanopb.proto";
+import "proto/timestamp.proto";
+import "proto/wrappers.proto";
+
+/**
+ * Each shard has a nonnegative shard number. Each realm within a given shard
+ * has a nonnegative realm number (that number might be reused in other
+ * shards). And each account, file, and smart contract instance within a given
+ * realm has a nonnegative number (which might be reused in other realms).
+ * Every account, file, and smart contract instance is within exactly one
+ * realm. So a FileID is a triplet of numbers, like 0.1.2 for entity number 2
+ * within realm 1 within shard 0. Each realm maintains a single counter for
+ * assigning numbers, so if there is a file with ID 0.1.2, then there won't be
+ * an account or smart contract instance with ID 0.1.2.
+ *
+ * Everything is partitioned into realms so that each Solidity smart contract
+ * can access everything in just a single realm, locking all those entities
+ * while it's running, but other smart contracts could potentially run in
+ * other realms in parallel. So realms allow Solidity to be parallelized
+ * somewhat, even though the language itself assumes everything is serial.
+ */
+message ShardID {
+ /**
+ * the shard number (nonnegative)
+ */
+ int64 shardNum = 1;
+}
+
+/**
+ * The ID for a realm. Within a given shard, every realm has a unique ID. Each
+ * account, file, and contract instance belongs to exactly one realm.
+ */
+message RealmID {
+ /**
+ * The shard number (nonnegative)
+ */
+ int64 shardNum = 1;
+
+ /**
+ * The realm number (nonnegative)
+ */
+ int64 realmNum = 2;
+}
+
+/**
+ * The ID for an a cryptocurrency account
+ */
+message AccountID {
+ /**
+ * The shard number (nonnegative)
+ */
+ int64 shardNum = 1;
+
+ /**
+ * The realm number (nonnegative)
+ */
+ int64 realmNum = 2;
+
+ /**
+ * The account number unique within its realm which can be either a
+ * non-negative integer or an alias public key. For any AccountID fields in
+ * the query response, transaction record or transaction receipt only
+ * accountNum will be populated.
+ */
+ oneof account {
+ /**
+ * A non-negative account number unique within its realm
+ */
+ int64 accountNum = 3;
+
+ /**
+ * The public key bytes to be used as the account's alias. The public key
+ * bytes are the result of serializing a protobuf Key message for any
+ * primitive key type. Currently only primitive key bytes are supported as
+ * an alias (ThresholdKey, KeyList, ContractID, and delegatable_contract_id
+ * are not supported)
+ *
+ * At most one account can ever have a given alias and it is used for
+ * account creation if it was automatically created using a crypto
+ * transfer. It will be null if an account is created normally. It is
+ * immutable once it is set for an account.
+ *
+ * If a transaction auto-creates the account, any further transfers to that
+ * alias will simply be deposited in that account, without creating
+ * anything, and with no creation fee being charged.
+ */
+ bytes alias = 4 [ (nanopb).max_size = 32 ];
+ }
+}
+
+/**
+ * The ID for a file
+ */
+message FileID {
+ /**
+ * The shard number (nonnegative)
+ */
+ int64 shardNum = 1;
+
+ /**
+ * The realm number (nonnegative)
+ */
+ int64 realmNum = 2;
+
+ /**
+ * A nonnegative File number unique within its realm
+ */
+ int64 fileNum = 3;
+}
+
+/**
+ * The ID for a smart contract instance
+ */
+message ContractID {
+ /**
+ * The shard number (nonnegative)
+ */
+ int64 shardNum = 1;
+
+ /**
+ * The realm number (nonnegative)
+ */
+ int64 realmNum = 2;
+
+ oneof contract {
+ /**
+ * A nonnegative number unique within a given shard and realm
+ */
+ int64 contractNum = 3;
+
+ /**
+ * The 20-byte EVM address of the contract to call.
+ *
+ * Every contract has an EVM address determined by its
+ * shard.realm.num id. This address is as follows:
- The
+ * first 4 bytes are the big-endian representation of the shard.
+ * - The next 8 bytes are the big-endian representation of the
+ * realm.
- The final 8 bytes are the big-endian representation of
+ * the number.
+ *
+ *
+ * Contracts created via CREATE2 have an additional, primary address
+ * that is derived from the EIP-1014
+ * specification, and does not have a simple relation to a
+ * shard.realm.num id.
+ *
+ * (Please do note that CREATE2 contracts can also be referenced by the
+ * three-part EVM address described above.)
+ */
+ bytes evm_address = 4 [ (nanopb).max_size = 20 ];
+ }
+}
+
+/**
+ * The ID for a transaction. This is used for retrieving receipts and records
+ * for a transaction, for appending to a file right after creating it, for
+ * instantiating a smart contract with bytecode in a file just created, and
+ * internally by the network for detecting when duplicate transactions are
+ * submitted. A user might get a transaction processed faster by submitting it
+ * to N nodes, each with a different node account, but all with the same
+ * TransactionID. Then, the transaction will take effect when the first of all
+ * those nodes submits the transaction and it reaches consensus. The other
+ * transactions will not take effect. So this could make the transaction take
+ * effect faster, if any given node might be slow. However, the full
+ * transaction fee is charged for each transaction, so the total fee is N times
+ * as much if the transaction is sent to N nodes.
+ *
+ * Applicable to Scheduled Transactions:
+ * - The ID of a Scheduled Transaction has transactionValidStart and
+ * accountIDs inherited from the ScheduleCreate transaction that created it.
+ * That is to say that they are equal
+ * - The scheduled property is true for Scheduled Transactions
+ * - transactionValidStart, accountID and scheduled properties should be
+ * omitted
+ */
+message TransactionID {
+ /**
+ * The transaction is invalid if consensusTimestamp <
+ * transactionID.transactionStartValid
+ */
+ Timestamp transactionValidStart = 1;
+
+ /**
+ * The Account ID that paid for this transaction
+ */
+ AccountID accountID = 2;
+
+ /**
+ * Whether the Transaction is of type Scheduled or no
+ */
+ bool scheduled = 3;
+
+ /**
+ * The identifier for an internal transaction that was spawned as part
+ * of handling a user transaction. (These internal transactions share the
+ * transactionValidStart and accountID of the user transaction, so a
+ * nonce is necessary to give them a unique TransactionID.)
+ *
+ * An example is when a "parent" ContractCreate or ContractCall transaction
+ * calls one or more HTS precompiled contracts; each of the "child"
+ * transactions spawned for a precompile has a id with a different nonce.
+ */
+ int32 nonce = 4;
+}
+
+/**
+ * An account, and the amount that it sends or receives during a cryptocurrency
+ * or token transfer.
+ */
+message AccountAmount {
+ /**
+ * The Account ID that sends/receives cryptocurrency or tokens
+ */
+ AccountID accountID = 1;
+
+ /**
+ * The amount of tinybars (for Crypto transfers) or in the lowest
+ * denomination (for Token transfers) that the account sends(negative) or
+ * receives(positive)
+ */
+ sint64 amount = 2;
+
+ /**
+ * If true then the transfer is expected to be an approved allowance and the
+ * accountID is expected to be the owner. The default is false (omitted).
+ */
+ bool is_approval = 3;
+}
+
+/**
+ * A list of accounts and amounts to transfer out of each account (negative) or
+ * into it (positive).
+ */
+message TransferList {
+ /**
+ * Multiple list of AccountAmount pairs, each of which has an account and
+ * an amount to transfer into it (positive) or out of it (negative)
+ * Limited to 2 for a transfer between two accounts
+ */
+ repeated AccountAmount accountAmounts = 1 [ (nanopb).max_count = 2 ];
+}
+
+/**
+ * A sender account, a receiver account, and the serial number of an NFT of a
+ * Token with NON_FUNGIBLE_UNIQUE type. When minting NFTs the sender will be
+ * the default AccountID instance (0.0.0) and when burning NFTs, the receiver
+ * will be the default AccountID instance.
+ */
+message NftTransfer {
+ /**
+ * The accountID of the sender
+ */
+ AccountID senderAccountID = 1;
+
+ /**
+ * The accountID of the receiver
+ */
+ AccountID receiverAccountID = 2;
+
+ /**
+ * The serial number of the NFT
+ */
+ int64 serialNumber = 3;
+
+ /**
+ * If true then the transfer is expected to be an approved allowance and the
+ * senderAccountID is expected to be the owner. The default is false
+ * (omitted).
+ */
+ bool is_approval = 4;
+}
+
+/**
+ * A list of token IDs and amounts representing the transferred out (negative)
+ * or into (positive) amounts, represented in the lowest denomination of the
+ * token
+ */
+message TokenTransferList {
+ /**
+ * The ID of the token
+ */
+ TokenID token = 1;
+
+ /**
+ * Applicable to tokens of type FUNGIBLE_COMMON. Multiple list of
+ * AccountAmounts, each of which has an account and amount
+ * Limited to 2 for 1 allowed transfer (reciprocal subtraction of balance +
+ * actual transfer)
+ */
+ repeated AccountAmount transfers = 2 [ (nanopb).max_count = 2 ];
+
+ /**
+ * Applicable to tokens of type NON_FUNGIBLE_UNIQUE. Multiple list of
+ * NftTransfers, each of which has a sender and receiver account, including
+ * the serial number of the NFT
+ * Limited to 1 here
+ */
+ repeated NftTransfer nftTransfers = 3 [ (nanopb).max_count = 1 ];
+
+ /**
+ * If present, the number of decimals this fungible token type is expected to
+ * have. The transfer will fail with UNEXPECTED_TOKEN_DECIMALS if the actual
+ * decimals differ.
+ */
+ UInt32Value expected_decimals = 4;
+}
+
+/**
+ * A rational number, used to set the amount of a value transfer to collect as
+ * a custom fee
+ */
+message Fraction {
+ /**
+ * The rational's numerator
+ */
+ int64 numerator = 1;
+
+ /**
+ * The rational's denominator; a zero value will result in
+ * FRACTION_DIVIDES_BY_ZERO
+ */
+ int64 denominator = 2;
+}
+
+/**
+ * Unique identifier for a token
+ */
+message TokenID {
+ /**
+ * A nonnegative shard number
+ */
+ int64 shardNum = 1;
+
+ /**
+ * A nonnegative realm number
+ */
+ int64 realmNum = 2;
+
+ /**
+ * A nonnegative token number
+ */
+ int64 tokenNum = 3;
+}
+
+/**
+ * A Key can be a public key from either the Ed25519 or ECDSA(secp256k1)
+ * signature schemes, where in the ECDSA(secp256k1) case we require the 33-byte
+ * compressed form of the public key. We call these public keys primitive
+ * keys.
+ *
+ * If an account has primitive key associated to it, then the corresponding
+ * private key must sign any transaction to transfer cryptocurrency out of it.
+ *
+ * A Key can also be the ID of a smart contract instance, which is then
+ * authorized to perform any precompiled contract action that requires this key
+ * to sign.
+ *
+ * Note that when a Key is a smart contract ID, it doesn't mean the
+ * contract with that ID will actually create a cryptographic signature. It
+ * only means that when the contract calls a precompiled contract, the
+ * resulting "child transaction" will be authorized to perform any action
+ * controlled by the Key.
+ *
+ * A Key can be a "threshold key", which means a list of M keys, any N of which
+ * must sign in order for the threshold signature to be considered valid. The
+ * keys within a threshold signature may themselves be threshold signatures, to
+ * allow complex signature requirements.
+ *
+ * A Key can be a "key list" where all keys in the list must sign unless
+ * specified otherwise in the documentation for a specific transaction type
+ * (e.g. FileDeleteTransactionBody). Their use is dependent on context. For
+ * example, a Hedera file is created with a list of keys, where all of them
+ * must sign a transaction to create or modify the file, but only one of them
+ * is needed to sign a transaction to delete the file. So it's a single list
+ * that sometimes acts as a 1-of-M threshold key, and sometimes acts as an
+ * M-of-M threshold key. A key list is always an M-of-M, unless specified
+ * otherwise in documentation. A key list can have nested key lists or
+ * threshold keys. Nested key lists are always M-of-M. A key list can have
+ * repeated primitive public keys, but all repeated keys are only required to
+ * sign once.
+ *
+ * A Key can contain a ThresholdKey or KeyList, which in turn contain a Key, so
+ * this mutual recursion would allow nesting arbitrarily deep. A ThresholdKey
+ * which contains a list of primitive keys has 3 levels: ThresholdKey ->
+ * KeyList
+ * -> Key. A KeyList which contains several primitive keys has 2 levels:
+ * KeyList
+ * -> Key. A Key with 2 levels of nested ThresholdKeys has 7 levels: Key ->
+ * ThresholdKey -> KeyList -> Key -> ThresholdKey -> KeyList -> Key.
+ *
+ * Each Key should not have more than 46 levels, which implies 15 levels of
+ * nested ThresholdKeys.
+ */
+message Key {
+ oneof key {
+ /**
+ * smart contract instance that is authorized as if it had signed with a
+ * key
+ */
+ ContractID contractID = 1;
+
+ /**
+ * Ed25519 public key bytes
+ */
+ bytes ed25519 = 2 [ (nanopb).max_size = 32 ];
+
+ /**
+ * (NOT SUPPORTED) RSA-3072 public key bytes
+ */
+ bytes RSA_3072 = 3 [ (nanopb).max_size = 32 ];
+
+ /**
+ * (NOT SUPPORTED) ECDSA with the p-384 curve public key bytes
+ */
+ bytes ECDSA_384 = 4 [ (nanopb).max_size = 32 ];
+
+ /**
+ * a threshold N followed by a list of M keys, any N of which are required
+ * to form a valid signature
+ * Leaving this in causes a circular dependecy
+ */
+ // ThresholdKey thresholdKey = 5;
+
+ /**
+ * A list of Keys of the Key type.
+ * Leaving this in causes a circular dependency
+ */
+ // KeyList keyList = 6;
+
+ /**
+ * Compressed ECDSA(secp256k1) public key bytes
+ */
+ bytes ECDSA_secp256k1 = 7 [ (nanopb).max_size = 32 ];
+
+ /**
+ * A smart contract that, if the recipient of the active message frame,
+ * should be treated as having signed. (Note this does not mean the code
+ * being executed in the frame will belong to the given contract, since
+ * it could be running another contract's code via delegatecall.
+ * So setting this key is a more permissive version of setting the
+ * contractID key, which also requires the code in the active message frame
+ * belong to the the contract with the given id.)
+ */
+ ContractID delegatable_contract_id = 8;
+ }
+}
+
+/**
+ * A set of public keys that are used together to form a threshold signature.
+ * If the threshold is N and there are M keys, then this is an N of M threshold
+ * signature. If an account is associated with ThresholdKeys, then a
+ * transaction to move cryptocurrency out of it must be signed by a list of M
+ * signatures, where at most M-N of them are blank, and the other at least N of
+ * them are valid signatures corresponding to at least N of the public keys
+ * listed here.
+ */
+message ThresholdKey {
+ /**
+ * A valid signature set must have at least this many signatures
+ */
+ uint32 threshold = 1;
+
+ /**
+ * List of all the keys that can sign
+ */
+ KeyList keys = 2;
+}
+
+/**
+ * A list of keys that requires all keys (M-of-M) to sign unless otherwise
+ * specified in documentation. A KeyList may contain repeated keys, but all
+ * repeated keys are only required to sign once.
+ */
+message KeyList {
+ /**
+ * list of keys
+ * Limited to 1 here (because we don't have malloc!)
+ */
+ repeated Key keys = 1 [ (nanopb).max_count = 1 ];
+}
+
+/**
+ * A number of transferable units of a certain token.
+ *
+ * The transferable unit of a token is its smallest denomination, as given by
+ * the token's decimals property---each minted token contains
+ * 10decimals transferable units. For example, we could
+ * think of the cent as the transferable unit of the US dollar
+ * (decimals=2); and the tinybar as the transferable unit of hbar
+ * (decimals=8).
+ *
+ * Transferable units are not directly comparable across different tokens.
+ */
+message TokenBalance {
+ /**
+ * A unique token id
+ */
+ TokenID tokenId = 1;
+
+ /**
+ * Number of transferable units of the identified token. For token of type
+ * FUNGIBLE_COMMON - balance in the smallest denomination. For token of type
+ * NON_FUNGIBLE_UNIQUE - the number of NFTs held by the account
+ */
+ uint64 balance = 2;
+
+ /**
+ * Tokens divide into 10decimals pieces
+ */
+ uint32 decimals = 3;
+}
+
+/**
+ * A sequence of token balances
+ * Limited to 1 here
+ */
+message TokenBalances {
+ repeated TokenBalance tokenBalances = 1 [ (nanopb).max_count = 1 ];
+}
+
+/* A token - account association */
+message TokenAssociation {
+ TokenID token_id = 1; // The token involved in the association
+ AccountID account_id = 2; // The account involved in the association
+}
+
+/**
+ * Staking metadata for an account or a contract returned in CryptoGetInfo or
+ * ContractGetInfo queries
+ */
+message StakingInfo {
+ /**
+ * If true, this account or contract declined to receive a staking reward.
+ */
+ bool decline_reward = 1;
+
+ /**
+ * The staking period during which either the staking settings for this
+ * account or contract changed (such as starting staking or changing
+ * staked_node_id) or the most recent reward was earned, whichever is later.
+ * If this account or contract is not currently staked to a node, then this
+ * field is not set.
+ */
+ Timestamp stake_period_start = 2;
+
+ /**
+ * The amount in tinybars that will be received in the next reward situation.
+ */
+ int64 pending_reward = 3;
+
+ /**
+ * The total of balance of all accounts staked to this account or contract.
+ */
+ int64 staked_to_me = 4;
+
+ /**
+ * ID of the account or node to which this account or contract is staking.
+ */
+ oneof staked_id {
+ /**
+ * The account to which this account or contract is staking.
+ */
+ AccountID staked_account_id = 5;
+
+ /**
+ * The ID of the node this account or contract is staked to.
+ */
+ int64 staked_node_id = 6;
+ }
+}
\ No newline at end of file
diff --git a/proto/basic_types_pb2.py b/proto/basic_types_pb2.py
new file mode 100644
index 00000000..1861ee2f
--- /dev/null
+++ b/proto/basic_types_pb2.py
@@ -0,0 +1,86 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: proto/basic_types.proto
+"""Generated protocol buffer code."""
+from google.protobuf.internal import builder as _builder
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+import nanopb_pb2 as nanopb__pb2
+from proto import timestamp_pb2 as proto_dot_timestamp__pb2
+from proto import wrappers_pb2 as proto_dot_wrappers__pb2
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17proto/basic_types.proto\x12\x06Hedera\x1a\x0cnanopb.proto\x1a\x15proto/timestamp.proto\x1a\x14proto/wrappers.proto\"\x1b\n\x07ShardID\x12\x10\n\x08shardNum\x18\x01 \x01(\x03\"-\n\x07RealmID\x12\x10\n\x08shardNum\x18\x01 \x01(\x03\x12\x10\n\x08realmNum\x18\x02 \x01(\x03\"h\n\tAccountID\x12\x10\n\x08shardNum\x18\x01 \x01(\x03\x12\x10\n\x08realmNum\x18\x02 \x01(\x03\x12\x14\n\naccountNum\x18\x03 \x01(\x03H\x00\x12\x16\n\x05\x61lias\x18\x04 \x01(\x0c\x42\x05\x92?\x02\x08 H\x00\x42\t\n\x07\x61\x63\x63ount\"=\n\x06\x46ileID\x12\x10\n\x08shardNum\x18\x01 \x01(\x03\x12\x10\n\x08realmNum\x18\x02 \x01(\x03\x12\x0f\n\x07\x66ileNum\x18\x03 \x01(\x03\"q\n\nContractID\x12\x10\n\x08shardNum\x18\x01 \x01(\x03\x12\x10\n\x08realmNum\x18\x02 \x01(\x03\x12\x15\n\x0b\x63ontractNum\x18\x03 \x01(\x03H\x00\x12\x1c\n\x0b\x65vm_address\x18\x04 \x01(\x0c\x42\x05\x92?\x02\x08\x14H\x00\x42\n\n\x08\x63ontract\"\x89\x01\n\rTransactionID\x12\x30\n\x15transactionValidStart\x18\x01 \x01(\x0b\x32\x11.Hedera.Timestamp\x12$\n\taccountID\x18\x02 \x01(\x0b\x32\x11.Hedera.AccountID\x12\x11\n\tscheduled\x18\x03 \x01(\x08\x12\r\n\x05nonce\x18\x04 \x01(\x05\"Z\n\rAccountAmount\x12$\n\taccountID\x18\x01 \x01(\x0b\x32\x11.Hedera.AccountID\x12\x0e\n\x06\x61mount\x18\x02 \x01(\x12\x12\x13\n\x0bis_approval\x18\x03 \x01(\x08\"D\n\x0cTransferList\x12\x34\n\x0e\x61\x63\x63ountAmounts\x18\x01 \x03(\x0b\x32\x15.Hedera.AccountAmountB\x05\x92?\x02\x10\x02\"\x92\x01\n\x0bNftTransfer\x12*\n\x0fsenderAccountID\x18\x01 \x01(\x0b\x32\x11.Hedera.AccountID\x12,\n\x11receiverAccountID\x18\x02 \x01(\x0b\x32\x11.Hedera.AccountID\x12\x14\n\x0cserialNumber\x18\x03 \x01(\x03\x12\x13\n\x0bis_approval\x18\x04 \x01(\x08\"\xc6\x01\n\x11TokenTransferList\x12\x1e\n\x05token\x18\x01 \x01(\x0b\x32\x0f.Hedera.TokenID\x12/\n\ttransfers\x18\x02 \x03(\x0b\x32\x15.Hedera.AccountAmountB\x05\x92?\x02\x10\x02\x12\x30\n\x0cnftTransfers\x18\x03 \x03(\x0b\x32\x13.Hedera.NftTransferB\x05\x92?\x02\x10\x01\x12.\n\x11\x65xpected_decimals\x18\x04 \x01(\x0b\x32\x13.Hedera.UInt32Value\"2\n\x08\x46raction\x12\x11\n\tnumerator\x18\x01 \x01(\x03\x12\x13\n\x0b\x64\x65nominator\x18\x02 \x01(\x03\"?\n\x07TokenID\x12\x10\n\x08shardNum\x18\x01 \x01(\x03\x12\x10\n\x08realmNum\x18\x02 \x01(\x03\x12\x10\n\x08tokenNum\x18\x03 \x01(\x03\"\xe0\x01\n\x03Key\x12(\n\ncontractID\x18\x01 \x01(\x0b\x32\x12.Hedera.ContractIDH\x00\x12\x18\n\x07\x65\x64\x32\x35\x35\x31\x39\x18\x02 \x01(\x0c\x42\x05\x92?\x02\x08 H\x00\x12\x19\n\x08RSA_3072\x18\x03 \x01(\x0c\x42\x05\x92?\x02\x08 H\x00\x12\x1a\n\tECDSA_384\x18\x04 \x01(\x0c\x42\x05\x92?\x02\x08 H\x00\x12 \n\x0f\x45\x43\x44SA_secp256k1\x18\x07 \x01(\x0c\x42\x05\x92?\x02\x08 H\x00\x12\x35\n\x17\x64\x65legatable_contract_id\x18\x08 \x01(\x0b\x32\x12.Hedera.ContractIDH\x00\x42\x05\n\x03key\"@\n\x0cThresholdKey\x12\x11\n\tthreshold\x18\x01 \x01(\r\x12\x1d\n\x04keys\x18\x02 \x01(\x0b\x32\x0f.Hedera.KeyList\"+\n\x07KeyList\x12 \n\x04keys\x18\x01 \x03(\x0b\x32\x0b.Hedera.KeyB\x05\x92?\x02\x10\x01\"S\n\x0cTokenBalance\x12 \n\x07tokenId\x18\x01 \x01(\x0b\x32\x0f.Hedera.TokenID\x12\x0f\n\x07\x62\x61lance\x18\x02 \x01(\x04\x12\x10\n\x08\x64\x65\x63imals\x18\x03 \x01(\r\"C\n\rTokenBalances\x12\x32\n\rtokenBalances\x18\x01 \x03(\x0b\x32\x14.Hedera.TokenBalanceB\x05\x92?\x02\x10\x01\"\\\n\x10TokenAssociation\x12!\n\x08token_id\x18\x01 \x01(\x0b\x32\x0f.Hedera.TokenID\x12%\n\naccount_id\x18\x02 \x01(\x0b\x32\x11.Hedera.AccountID\"\xd9\x01\n\x0bStakingInfo\x12\x16\n\x0e\x64\x65\x63line_reward\x18\x01 \x01(\x08\x12-\n\x12stake_period_start\x18\x02 \x01(\x0b\x32\x11.Hedera.Timestamp\x12\x16\n\x0epending_reward\x18\x03 \x01(\x03\x12\x14\n\x0cstaked_to_me\x18\x04 \x01(\x03\x12.\n\x11staked_account_id\x18\x05 \x01(\x0b\x32\x11.Hedera.AccountIDH\x00\x12\x18\n\x0estaked_node_id\x18\x06 \x01(\x03H\x00\x42\x0b\n\tstaked_idb\x06proto3')
+
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'proto.basic_types_pb2', globals())
+if _descriptor._USE_C_DESCRIPTORS == False:
+
+ DESCRIPTOR._options = None
+ _ACCOUNTID.fields_by_name['alias']._options = None
+ _ACCOUNTID.fields_by_name['alias']._serialized_options = b'\222?\002\010 '
+ _CONTRACTID.fields_by_name['evm_address']._options = None
+ _CONTRACTID.fields_by_name['evm_address']._serialized_options = b'\222?\002\010\024'
+ _TRANSFERLIST.fields_by_name['accountAmounts']._options = None
+ _TRANSFERLIST.fields_by_name['accountAmounts']._serialized_options = b'\222?\002\020\002'
+ _TOKENTRANSFERLIST.fields_by_name['transfers']._options = None
+ _TOKENTRANSFERLIST.fields_by_name['transfers']._serialized_options = b'\222?\002\020\002'
+ _TOKENTRANSFERLIST.fields_by_name['nftTransfers']._options = None
+ _TOKENTRANSFERLIST.fields_by_name['nftTransfers']._serialized_options = b'\222?\002\020\001'
+ _KEY.fields_by_name['ed25519']._options = None
+ _KEY.fields_by_name['ed25519']._serialized_options = b'\222?\002\010 '
+ _KEY.fields_by_name['RSA_3072']._options = None
+ _KEY.fields_by_name['RSA_3072']._serialized_options = b'\222?\002\010 '
+ _KEY.fields_by_name['ECDSA_384']._options = None
+ _KEY.fields_by_name['ECDSA_384']._serialized_options = b'\222?\002\010 '
+ _KEY.fields_by_name['ECDSA_secp256k1']._options = None
+ _KEY.fields_by_name['ECDSA_secp256k1']._serialized_options = b'\222?\002\010 '
+ _KEYLIST.fields_by_name['keys']._options = None
+ _KEYLIST.fields_by_name['keys']._serialized_options = b'\222?\002\020\001'
+ _TOKENBALANCES.fields_by_name['tokenBalances']._options = None
+ _TOKENBALANCES.fields_by_name['tokenBalances']._serialized_options = b'\222?\002\020\001'
+ _SHARDID._serialized_start=94
+ _SHARDID._serialized_end=121
+ _REALMID._serialized_start=123
+ _REALMID._serialized_end=168
+ _ACCOUNTID._serialized_start=170
+ _ACCOUNTID._serialized_end=274
+ _FILEID._serialized_start=276
+ _FILEID._serialized_end=337
+ _CONTRACTID._serialized_start=339
+ _CONTRACTID._serialized_end=452
+ _TRANSACTIONID._serialized_start=455
+ _TRANSACTIONID._serialized_end=592
+ _ACCOUNTAMOUNT._serialized_start=594
+ _ACCOUNTAMOUNT._serialized_end=684
+ _TRANSFERLIST._serialized_start=686
+ _TRANSFERLIST._serialized_end=754
+ _NFTTRANSFER._serialized_start=757
+ _NFTTRANSFER._serialized_end=903
+ _TOKENTRANSFERLIST._serialized_start=906
+ _TOKENTRANSFERLIST._serialized_end=1104
+ _FRACTION._serialized_start=1106
+ _FRACTION._serialized_end=1156
+ _TOKENID._serialized_start=1158
+ _TOKENID._serialized_end=1221
+ _KEY._serialized_start=1224
+ _KEY._serialized_end=1448
+ _THRESHOLDKEY._serialized_start=1450
+ _THRESHOLDKEY._serialized_end=1514
+ _KEYLIST._serialized_start=1516
+ _KEYLIST._serialized_end=1559
+ _TOKENBALANCE._serialized_start=1561
+ _TOKENBALANCE._serialized_end=1644
+ _TOKENBALANCES._serialized_start=1646
+ _TOKENBALANCES._serialized_end=1713
+ _TOKENASSOCIATION._serialized_start=1715
+ _TOKENASSOCIATION._serialized_end=1807
+ _STAKINGINFO._serialized_start=1810
+ _STAKINGINFO._serialized_end=2027
+# @@protoc_insertion_point(module_scope)
diff --git a/proto/basic_types_pb2.pyi b/proto/basic_types_pb2.pyi
new file mode 100644
index 00000000..9707db1c
--- /dev/null
+++ b/proto/basic_types_pb2.pyi
@@ -0,0 +1,862 @@
+"""
+@generated by mypy-protobuf. Do not edit manually!
+isort:skip_file
+"""
+import builtins
+import collections.abc
+import google.protobuf.descriptor
+import google.protobuf.internal.containers
+import google.protobuf.message
+import proto.timestamp_pb2
+import proto.wrappers_pb2
+import sys
+
+if sys.version_info >= (3, 8):
+ import typing as typing_extensions
+else:
+ import typing_extensions
+
+DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
+
+@typing_extensions.final
+class ShardID(google.protobuf.message.Message):
+ """*
+ Each shard has a nonnegative shard number. Each realm within a given shard
+ has a nonnegative realm number (that number might be reused in other
+ shards). And each account, file, and smart contract instance within a given
+ realm has a nonnegative number (which might be reused in other realms).
+ Every account, file, and smart contract instance is within exactly one
+ realm. So a FileID is a triplet of numbers, like 0.1.2 for entity number 2
+ within realm 1 within shard 0. Each realm maintains a single counter for
+ assigning numbers, so if there is a file with ID 0.1.2, then there won't be
+ an account or smart contract instance with ID 0.1.2.
+
+ Everything is partitioned into realms so that each Solidity smart contract
+ can access everything in just a single realm, locking all those entities
+ while it's running, but other smart contracts could potentially run in
+ other realms in parallel. So realms allow Solidity to be parallelized
+ somewhat, even though the language itself assumes everything is serial.
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ SHARDNUM_FIELD_NUMBER: builtins.int
+ shardNum: builtins.int
+ """*
+ the shard number (nonnegative)
+ """
+ def __init__(
+ self,
+ *,
+ shardNum: builtins.int = ...,
+ ) -> None: ...
+ def ClearField(self, field_name: typing_extensions.Literal["shardNum", b"shardNum"]) -> None: ...
+
+global___ShardID = ShardID
+
+@typing_extensions.final
+class RealmID(google.protobuf.message.Message):
+ """*
+ The ID for a realm. Within a given shard, every realm has a unique ID. Each
+ account, file, and contract instance belongs to exactly one realm.
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ SHARDNUM_FIELD_NUMBER: builtins.int
+ REALMNUM_FIELD_NUMBER: builtins.int
+ shardNum: builtins.int
+ """*
+ The shard number (nonnegative)
+ """
+ realmNum: builtins.int
+ """*
+ The realm number (nonnegative)
+ """
+ def __init__(
+ self,
+ *,
+ shardNum: builtins.int = ...,
+ realmNum: builtins.int = ...,
+ ) -> None: ...
+ def ClearField(self, field_name: typing_extensions.Literal["realmNum", b"realmNum", "shardNum", b"shardNum"]) -> None: ...
+
+global___RealmID = RealmID
+
+@typing_extensions.final
+class AccountID(google.protobuf.message.Message):
+ """*
+ The ID for an a cryptocurrency account
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ SHARDNUM_FIELD_NUMBER: builtins.int
+ REALMNUM_FIELD_NUMBER: builtins.int
+ ACCOUNTNUM_FIELD_NUMBER: builtins.int
+ ALIAS_FIELD_NUMBER: builtins.int
+ shardNum: builtins.int
+ """*
+ The shard number (nonnegative)
+ """
+ realmNum: builtins.int
+ """*
+ The realm number (nonnegative)
+ """
+ accountNum: builtins.int
+ """*
+ A non-negative account number unique within its realm
+ """
+ alias: builtins.bytes
+ """*
+ The public key bytes to be used as the account's alias. The public key
+ bytes are the result of serializing a protobuf Key message for any
+ primitive key type. Currently only primitive key bytes are supported as
+ an alias (ThresholdKey, KeyList, ContractID, and delegatable_contract_id
+ are not supported)
+
+ At most one account can ever have a given alias and it is used for
+ account creation if it was automatically created using a crypto
+ transfer. It will be null if an account is created normally. It is
+ immutable once it is set for an account.
+
+ If a transaction auto-creates the account, any further transfers to that
+ alias will simply be deposited in that account, without creating
+ anything, and with no creation fee being charged.
+ """
+ def __init__(
+ self,
+ *,
+ shardNum: builtins.int = ...,
+ realmNum: builtins.int = ...,
+ accountNum: builtins.int = ...,
+ alias: builtins.bytes = ...,
+ ) -> None: ...
+ def HasField(self, field_name: typing_extensions.Literal["account", b"account", "accountNum", b"accountNum", "alias", b"alias"]) -> builtins.bool: ...
+ def ClearField(self, field_name: typing_extensions.Literal["account", b"account", "accountNum", b"accountNum", "alias", b"alias", "realmNum", b"realmNum", "shardNum", b"shardNum"]) -> None: ...
+ def WhichOneof(self, oneof_group: typing_extensions.Literal["account", b"account"]) -> typing_extensions.Literal["accountNum", "alias"] | None: ...
+
+global___AccountID = AccountID
+
+@typing_extensions.final
+class FileID(google.protobuf.message.Message):
+ """*
+ The ID for a file
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ SHARDNUM_FIELD_NUMBER: builtins.int
+ REALMNUM_FIELD_NUMBER: builtins.int
+ FILENUM_FIELD_NUMBER: builtins.int
+ shardNum: builtins.int
+ """*
+ The shard number (nonnegative)
+ """
+ realmNum: builtins.int
+ """*
+ The realm number (nonnegative)
+ """
+ fileNum: builtins.int
+ """*
+ A nonnegative File number unique within its realm
+ """
+ def __init__(
+ self,
+ *,
+ shardNum: builtins.int = ...,
+ realmNum: builtins.int = ...,
+ fileNum: builtins.int = ...,
+ ) -> None: ...
+ def ClearField(self, field_name: typing_extensions.Literal["fileNum", b"fileNum", "realmNum", b"realmNum", "shardNum", b"shardNum"]) -> None: ...
+
+global___FileID = FileID
+
+@typing_extensions.final
+class ContractID(google.protobuf.message.Message):
+ """*
+ The ID for a smart contract instance
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ SHARDNUM_FIELD_NUMBER: builtins.int
+ REALMNUM_FIELD_NUMBER: builtins.int
+ CONTRACTNUM_FIELD_NUMBER: builtins.int
+ EVM_ADDRESS_FIELD_NUMBER: builtins.int
+ shardNum: builtins.int
+ """*
+ The shard number (nonnegative)
+ """
+ realmNum: builtins.int
+ """*
+ The realm number (nonnegative)
+ """
+ contractNum: builtins.int
+ """*
+ A nonnegative number unique within a given shard and realm
+ """
+ evm_address: builtins.bytes
+ """*
+ The 20-byte EVM address of the contract to call.
+
+ Every contract has an EVM address determined by its
+ shard.realm.num id. This address is as follows: - The
+ first 4 bytes are the big-endian representation of the shard.
+ - The next 8 bytes are the big-endian representation of the
+ realm.
- The final 8 bytes are the big-endian representation of
+ the number.
+
+
+ Contracts created via CREATE2 have an additional, primary address
+ that is derived from the EIP-1014
+ specification, and does not have a simple relation to a
+ shard.realm.num id.
+
+ (Please do note that CREATE2 contracts can also be referenced by the
+ three-part EVM address described above.)
+ """
+ def __init__(
+ self,
+ *,
+ shardNum: builtins.int = ...,
+ realmNum: builtins.int = ...,
+ contractNum: builtins.int = ...,
+ evm_address: builtins.bytes = ...,
+ ) -> None: ...
+ def HasField(self, field_name: typing_extensions.Literal["contract", b"contract", "contractNum", b"contractNum", "evm_address", b"evm_address"]) -> builtins.bool: ...
+ def ClearField(self, field_name: typing_extensions.Literal["contract", b"contract", "contractNum", b"contractNum", "evm_address", b"evm_address", "realmNum", b"realmNum", "shardNum", b"shardNum"]) -> None: ...
+ def WhichOneof(self, oneof_group: typing_extensions.Literal["contract", b"contract"]) -> typing_extensions.Literal["contractNum", "evm_address"] | None: ...
+
+global___ContractID = ContractID
+
+@typing_extensions.final
+class TransactionID(google.protobuf.message.Message):
+ """*
+ The ID for a transaction. This is used for retrieving receipts and records
+ for a transaction, for appending to a file right after creating it, for
+ instantiating a smart contract with bytecode in a file just created, and
+ internally by the network for detecting when duplicate transactions are
+ submitted. A user might get a transaction processed faster by submitting it
+ to N nodes, each with a different node account, but all with the same
+ TransactionID. Then, the transaction will take effect when the first of all
+ those nodes submits the transaction and it reaches consensus. The other
+ transactions will not take effect. So this could make the transaction take
+ effect faster, if any given node might be slow. However, the full
+ transaction fee is charged for each transaction, so the total fee is N times
+ as much if the transaction is sent to N nodes.
+
+ Applicable to Scheduled Transactions:
+ - The ID of a Scheduled Transaction has transactionValidStart and
+ accountIDs inherited from the ScheduleCreate transaction that created it.
+ That is to say that they are equal
+ - The scheduled property is true for Scheduled Transactions
+ - transactionValidStart, accountID and scheduled properties should be
+ omitted
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ TRANSACTIONVALIDSTART_FIELD_NUMBER: builtins.int
+ ACCOUNTID_FIELD_NUMBER: builtins.int
+ SCHEDULED_FIELD_NUMBER: builtins.int
+ NONCE_FIELD_NUMBER: builtins.int
+ @property
+ def transactionValidStart(self) -> proto.timestamp_pb2.Timestamp:
+ """*
+ The transaction is invalid if consensusTimestamp <
+ transactionID.transactionStartValid
+ """
+ @property
+ def accountID(self) -> global___AccountID:
+ """*
+ The Account ID that paid for this transaction
+ """
+ scheduled: builtins.bool
+ """*
+ Whether the Transaction is of type Scheduled or no
+ """
+ nonce: builtins.int
+ """*
+ The identifier for an internal transaction that was spawned as part
+ of handling a user transaction. (These internal transactions share the
+ transactionValidStart and accountID of the user transaction, so a
+ nonce is necessary to give them a unique TransactionID.)
+
+ An example is when a "parent" ContractCreate or ContractCall transaction
+ calls one or more HTS precompiled contracts; each of the "child"
+ transactions spawned for a precompile has a id with a different nonce.
+ """
+ def __init__(
+ self,
+ *,
+ transactionValidStart: proto.timestamp_pb2.Timestamp | None = ...,
+ accountID: global___AccountID | None = ...,
+ scheduled: builtins.bool = ...,
+ nonce: builtins.int = ...,
+ ) -> None: ...
+ def HasField(self, field_name: typing_extensions.Literal["accountID", b"accountID", "transactionValidStart", b"transactionValidStart"]) -> builtins.bool: ...
+ def ClearField(self, field_name: typing_extensions.Literal["accountID", b"accountID", "nonce", b"nonce", "scheduled", b"scheduled", "transactionValidStart", b"transactionValidStart"]) -> None: ...
+
+global___TransactionID = TransactionID
+
+@typing_extensions.final
+class AccountAmount(google.protobuf.message.Message):
+ """*
+ An account, and the amount that it sends or receives during a cryptocurrency
+ or token transfer.
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ ACCOUNTID_FIELD_NUMBER: builtins.int
+ AMOUNT_FIELD_NUMBER: builtins.int
+ IS_APPROVAL_FIELD_NUMBER: builtins.int
+ @property
+ def accountID(self) -> global___AccountID:
+ """*
+ The Account ID that sends/receives cryptocurrency or tokens
+ """
+ amount: builtins.int
+ """*
+ The amount of tinybars (for Crypto transfers) or in the lowest
+ denomination (for Token transfers) that the account sends(negative) or
+ receives(positive)
+ """
+ is_approval: builtins.bool
+ """*
+ If true then the transfer is expected to be an approved allowance and the
+ accountID is expected to be the owner. The default is false (omitted).
+ """
+ def __init__(
+ self,
+ *,
+ accountID: global___AccountID | None = ...,
+ amount: builtins.int = ...,
+ is_approval: builtins.bool = ...,
+ ) -> None: ...
+ def HasField(self, field_name: typing_extensions.Literal["accountID", b"accountID"]) -> builtins.bool: ...
+ def ClearField(self, field_name: typing_extensions.Literal["accountID", b"accountID", "amount", b"amount", "is_approval", b"is_approval"]) -> None: ...
+
+global___AccountAmount = AccountAmount
+
+@typing_extensions.final
+class TransferList(google.protobuf.message.Message):
+ """*
+ A list of accounts and amounts to transfer out of each account (negative) or
+ into it (positive).
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ ACCOUNTAMOUNTS_FIELD_NUMBER: builtins.int
+ @property
+ def accountAmounts(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___AccountAmount]:
+ """*
+ Multiple list of AccountAmount pairs, each of which has an account and
+ an amount to transfer into it (positive) or out of it (negative)
+ Limited to 2 for a transfer between two accounts
+ """
+ def __init__(
+ self,
+ *,
+ accountAmounts: collections.abc.Iterable[global___AccountAmount] | None = ...,
+ ) -> None: ...
+ def ClearField(self, field_name: typing_extensions.Literal["accountAmounts", b"accountAmounts"]) -> None: ...
+
+global___TransferList = TransferList
+
+@typing_extensions.final
+class NftTransfer(google.protobuf.message.Message):
+ """*
+ A sender account, a receiver account, and the serial number of an NFT of a
+ Token with NON_FUNGIBLE_UNIQUE type. When minting NFTs the sender will be
+ the default AccountID instance (0.0.0) and when burning NFTs, the receiver
+ will be the default AccountID instance.
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ SENDERACCOUNTID_FIELD_NUMBER: builtins.int
+ RECEIVERACCOUNTID_FIELD_NUMBER: builtins.int
+ SERIALNUMBER_FIELD_NUMBER: builtins.int
+ IS_APPROVAL_FIELD_NUMBER: builtins.int
+ @property
+ def senderAccountID(self) -> global___AccountID:
+ """*
+ The accountID of the sender
+ """
+ @property
+ def receiverAccountID(self) -> global___AccountID:
+ """*
+ The accountID of the receiver
+ """
+ serialNumber: builtins.int
+ """*
+ The serial number of the NFT
+ """
+ is_approval: builtins.bool
+ """*
+ If true then the transfer is expected to be an approved allowance and the
+ senderAccountID is expected to be the owner. The default is false
+ (omitted).
+ """
+ def __init__(
+ self,
+ *,
+ senderAccountID: global___AccountID | None = ...,
+ receiverAccountID: global___AccountID | None = ...,
+ serialNumber: builtins.int = ...,
+ is_approval: builtins.bool = ...,
+ ) -> None: ...
+ def HasField(self, field_name: typing_extensions.Literal["receiverAccountID", b"receiverAccountID", "senderAccountID", b"senderAccountID"]) -> builtins.bool: ...
+ def ClearField(self, field_name: typing_extensions.Literal["is_approval", b"is_approval", "receiverAccountID", b"receiverAccountID", "senderAccountID", b"senderAccountID", "serialNumber", b"serialNumber"]) -> None: ...
+
+global___NftTransfer = NftTransfer
+
+@typing_extensions.final
+class TokenTransferList(google.protobuf.message.Message):
+ """*
+ A list of token IDs and amounts representing the transferred out (negative)
+ or into (positive) amounts, represented in the lowest denomination of the
+ token
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ TOKEN_FIELD_NUMBER: builtins.int
+ TRANSFERS_FIELD_NUMBER: builtins.int
+ NFTTRANSFERS_FIELD_NUMBER: builtins.int
+ EXPECTED_DECIMALS_FIELD_NUMBER: builtins.int
+ @property
+ def token(self) -> global___TokenID:
+ """*
+ The ID of the token
+ """
+ @property
+ def transfers(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___AccountAmount]:
+ """*
+ Applicable to tokens of type FUNGIBLE_COMMON. Multiple list of
+ AccountAmounts, each of which has an account and amount
+ Limited to 2 for 1 allowed transfer (reciprocal subtraction of balance +
+ actual transfer)
+ """
+ @property
+ def nftTransfers(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___NftTransfer]:
+ """*
+ Applicable to tokens of type NON_FUNGIBLE_UNIQUE. Multiple list of
+ NftTransfers, each of which has a sender and receiver account, including
+ the serial number of the NFT
+ Limited to 1 here
+ """
+ @property
+ def expected_decimals(self) -> proto.wrappers_pb2.UInt32Value:
+ """*
+ If present, the number of decimals this fungible token type is expected to
+ have. The transfer will fail with UNEXPECTED_TOKEN_DECIMALS if the actual
+ decimals differ.
+ """
+ def __init__(
+ self,
+ *,
+ token: global___TokenID | None = ...,
+ transfers: collections.abc.Iterable[global___AccountAmount] | None = ...,
+ nftTransfers: collections.abc.Iterable[global___NftTransfer] | None = ...,
+ expected_decimals: proto.wrappers_pb2.UInt32Value | None = ...,
+ ) -> None: ...
+ def HasField(self, field_name: typing_extensions.Literal["expected_decimals", b"expected_decimals", "token", b"token"]) -> builtins.bool: ...
+ def ClearField(self, field_name: typing_extensions.Literal["expected_decimals", b"expected_decimals", "nftTransfers", b"nftTransfers", "token", b"token", "transfers", b"transfers"]) -> None: ...
+
+global___TokenTransferList = TokenTransferList
+
+@typing_extensions.final
+class Fraction(google.protobuf.message.Message):
+ """*
+ A rational number, used to set the amount of a value transfer to collect as
+ a custom fee
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ NUMERATOR_FIELD_NUMBER: builtins.int
+ DENOMINATOR_FIELD_NUMBER: builtins.int
+ numerator: builtins.int
+ """*
+ The rational's numerator
+ """
+ denominator: builtins.int
+ """*
+ The rational's denominator; a zero value will result in
+ FRACTION_DIVIDES_BY_ZERO
+ """
+ def __init__(
+ self,
+ *,
+ numerator: builtins.int = ...,
+ denominator: builtins.int = ...,
+ ) -> None: ...
+ def ClearField(self, field_name: typing_extensions.Literal["denominator", b"denominator", "numerator", b"numerator"]) -> None: ...
+
+global___Fraction = Fraction
+
+@typing_extensions.final
+class TokenID(google.protobuf.message.Message):
+ """*
+ Unique identifier for a token
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ SHARDNUM_FIELD_NUMBER: builtins.int
+ REALMNUM_FIELD_NUMBER: builtins.int
+ TOKENNUM_FIELD_NUMBER: builtins.int
+ shardNum: builtins.int
+ """*
+ A nonnegative shard number
+ """
+ realmNum: builtins.int
+ """*
+ A nonnegative realm number
+ """
+ tokenNum: builtins.int
+ """*
+ A nonnegative token number
+ """
+ def __init__(
+ self,
+ *,
+ shardNum: builtins.int = ...,
+ realmNum: builtins.int = ...,
+ tokenNum: builtins.int = ...,
+ ) -> None: ...
+ def ClearField(self, field_name: typing_extensions.Literal["realmNum", b"realmNum", "shardNum", b"shardNum", "tokenNum", b"tokenNum"]) -> None: ...
+
+global___TokenID = TokenID
+
+@typing_extensions.final
+class Key(google.protobuf.message.Message):
+ """*
+ A Key can be a public key from either the Ed25519 or ECDSA(secp256k1)
+ signature schemes, where in the ECDSA(secp256k1) case we require the 33-byte
+ compressed form of the public key. We call these public keys primitive
+ keys.
+
+ If an account has primitive key associated to it, then the corresponding
+ private key must sign any transaction to transfer cryptocurrency out of it.
+
+ A Key can also be the ID of a smart contract instance, which is then
+ authorized to perform any precompiled contract action that requires this key
+ to sign.
+
+ Note that when a Key is a smart contract ID, it doesn't mean the
+ contract with that ID will actually create a cryptographic signature. It
+ only means that when the contract calls a precompiled contract, the
+ resulting "child transaction" will be authorized to perform any action
+ controlled by the Key.
+
+ A Key can be a "threshold key", which means a list of M keys, any N of which
+ must sign in order for the threshold signature to be considered valid. The
+ keys within a threshold signature may themselves be threshold signatures, to
+ allow complex signature requirements.
+
+ A Key can be a "key list" where all keys in the list must sign unless
+ specified otherwise in the documentation for a specific transaction type
+ (e.g. FileDeleteTransactionBody). Their use is dependent on context. For
+ example, a Hedera file is created with a list of keys, where all of them
+ must sign a transaction to create or modify the file, but only one of them
+ is needed to sign a transaction to delete the file. So it's a single list
+ that sometimes acts as a 1-of-M threshold key, and sometimes acts as an
+ M-of-M threshold key. A key list is always an M-of-M, unless specified
+ otherwise in documentation. A key list can have nested key lists or
+ threshold keys. Nested key lists are always M-of-M. A key list can have
+ repeated primitive public keys, but all repeated keys are only required to
+ sign once.
+
+ A Key can contain a ThresholdKey or KeyList, which in turn contain a Key, so
+ this mutual recursion would allow nesting arbitrarily deep. A ThresholdKey
+ which contains a list of primitive keys has 3 levels: ThresholdKey ->
+ KeyList
+ -> Key. A KeyList which contains several primitive keys has 2 levels:
+ KeyList
+ -> Key. A Key with 2 levels of nested ThresholdKeys has 7 levels: Key ->
+ ThresholdKey -> KeyList -> Key -> ThresholdKey -> KeyList -> Key.
+
+ Each Key should not have more than 46 levels, which implies 15 levels of
+ nested ThresholdKeys.
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ CONTRACTID_FIELD_NUMBER: builtins.int
+ ED25519_FIELD_NUMBER: builtins.int
+ RSA_3072_FIELD_NUMBER: builtins.int
+ ECDSA_384_FIELD_NUMBER: builtins.int
+ ECDSA_SECP256K1_FIELD_NUMBER: builtins.int
+ DELEGATABLE_CONTRACT_ID_FIELD_NUMBER: builtins.int
+ @property
+ def contractID(self) -> global___ContractID:
+ """*
+ smart contract instance that is authorized as if it had signed with a
+ key
+ """
+ ed25519: builtins.bytes
+ """*
+ Ed25519 public key bytes
+ """
+ RSA_3072: builtins.bytes
+ """*
+ (NOT SUPPORTED) RSA-3072 public key bytes
+ """
+ ECDSA_384: builtins.bytes
+ """*
+ (NOT SUPPORTED) ECDSA with the p-384 curve public key bytes
+ """
+ ECDSA_secp256k1: builtins.bytes
+ """KeyList keyList = 6;
+
+ *
+ Compressed ECDSA(secp256k1) public key bytes
+ """
+ @property
+ def delegatable_contract_id(self) -> global___ContractID:
+ """*
+ A smart contract that, if the recipient of the active message frame,
+ should be treated as having signed. (Note this does not mean the code
+ being executed in the frame will belong to the given contract, since
+ it could be running another contract's code via delegatecall.
+ So setting this key is a more permissive version of setting the
+ contractID key, which also requires the code in the active message frame
+ belong to the the contract with the given id.)
+ """
+ def __init__(
+ self,
+ *,
+ contractID: global___ContractID | None = ...,
+ ed25519: builtins.bytes = ...,
+ RSA_3072: builtins.bytes = ...,
+ ECDSA_384: builtins.bytes = ...,
+ ECDSA_secp256k1: builtins.bytes = ...,
+ delegatable_contract_id: global___ContractID | None = ...,
+ ) -> None: ...
+ def HasField(self, field_name: typing_extensions.Literal["ECDSA_384", b"ECDSA_384", "ECDSA_secp256k1", b"ECDSA_secp256k1", "RSA_3072", b"RSA_3072", "contractID", b"contractID", "delegatable_contract_id", b"delegatable_contract_id", "ed25519", b"ed25519", "key", b"key"]) -> builtins.bool: ...
+ def ClearField(self, field_name: typing_extensions.Literal["ECDSA_384", b"ECDSA_384", "ECDSA_secp256k1", b"ECDSA_secp256k1", "RSA_3072", b"RSA_3072", "contractID", b"contractID", "delegatable_contract_id", b"delegatable_contract_id", "ed25519", b"ed25519", "key", b"key"]) -> None: ...
+ def WhichOneof(self, oneof_group: typing_extensions.Literal["key", b"key"]) -> typing_extensions.Literal["contractID", "ed25519", "RSA_3072", "ECDSA_384", "ECDSA_secp256k1", "delegatable_contract_id"] | None: ...
+
+global___Key = Key
+
+@typing_extensions.final
+class ThresholdKey(google.protobuf.message.Message):
+ """*
+ A set of public keys that are used together to form a threshold signature.
+ If the threshold is N and there are M keys, then this is an N of M threshold
+ signature. If an account is associated with ThresholdKeys, then a
+ transaction to move cryptocurrency out of it must be signed by a list of M
+ signatures, where at most M-N of them are blank, and the other at least N of
+ them are valid signatures corresponding to at least N of the public keys
+ listed here.
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ THRESHOLD_FIELD_NUMBER: builtins.int
+ KEYS_FIELD_NUMBER: builtins.int
+ threshold: builtins.int
+ """*
+ A valid signature set must have at least this many signatures
+ """
+ @property
+ def keys(self) -> global___KeyList:
+ """*
+ List of all the keys that can sign
+ """
+ def __init__(
+ self,
+ *,
+ threshold: builtins.int = ...,
+ keys: global___KeyList | None = ...,
+ ) -> None: ...
+ def HasField(self, field_name: typing_extensions.Literal["keys", b"keys"]) -> builtins.bool: ...
+ def ClearField(self, field_name: typing_extensions.Literal["keys", b"keys", "threshold", b"threshold"]) -> None: ...
+
+global___ThresholdKey = ThresholdKey
+
+@typing_extensions.final
+class KeyList(google.protobuf.message.Message):
+ """*
+ A list of keys that requires all keys (M-of-M) to sign unless otherwise
+ specified in documentation. A KeyList may contain repeated keys, but all
+ repeated keys are only required to sign once.
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ KEYS_FIELD_NUMBER: builtins.int
+ @property
+ def keys(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Key]:
+ """*
+ list of keys
+ Limited to 1 here (because we don't have malloc!)
+ """
+ def __init__(
+ self,
+ *,
+ keys: collections.abc.Iterable[global___Key] | None = ...,
+ ) -> None: ...
+ def ClearField(self, field_name: typing_extensions.Literal["keys", b"keys"]) -> None: ...
+
+global___KeyList = KeyList
+
+@typing_extensions.final
+class TokenBalance(google.protobuf.message.Message):
+ """*
+ A number of transferable units of a certain token.
+
+ The transferable unit of a token is its smallest denomination, as given by
+ the token's decimals property---each minted token contains
+ 10decimals transferable units. For example, we could
+ think of the cent as the transferable unit of the US dollar
+ (decimals=2); and the tinybar as the transferable unit of hbar
+ (decimals=8).
+
+ Transferable units are not directly comparable across different tokens.
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ TOKENID_FIELD_NUMBER: builtins.int
+ BALANCE_FIELD_NUMBER: builtins.int
+ DECIMALS_FIELD_NUMBER: builtins.int
+ @property
+ def tokenId(self) -> global___TokenID:
+ """*
+ A unique token id
+ """
+ balance: builtins.int
+ """*
+ Number of transferable units of the identified token. For token of type
+ FUNGIBLE_COMMON - balance in the smallest denomination. For token of type
+ NON_FUNGIBLE_UNIQUE - the number of NFTs held by the account
+ """
+ decimals: builtins.int
+ """*
+ Tokens divide into 10decimals pieces
+ """
+ def __init__(
+ self,
+ *,
+ tokenId: global___TokenID | None = ...,
+ balance: builtins.int = ...,
+ decimals: builtins.int = ...,
+ ) -> None: ...
+ def HasField(self, field_name: typing_extensions.Literal["tokenId", b"tokenId"]) -> builtins.bool: ...
+ def ClearField(self, field_name: typing_extensions.Literal["balance", b"balance", "decimals", b"decimals", "tokenId", b"tokenId"]) -> None: ...
+
+global___TokenBalance = TokenBalance
+
+@typing_extensions.final
+class TokenBalances(google.protobuf.message.Message):
+ """*
+ A sequence of token balances
+ Limited to 1 here
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ TOKENBALANCES_FIELD_NUMBER: builtins.int
+ @property
+ def tokenBalances(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___TokenBalance]: ...
+ def __init__(
+ self,
+ *,
+ tokenBalances: collections.abc.Iterable[global___TokenBalance] | None = ...,
+ ) -> None: ...
+ def ClearField(self, field_name: typing_extensions.Literal["tokenBalances", b"tokenBalances"]) -> None: ...
+
+global___TokenBalances = TokenBalances
+
+@typing_extensions.final
+class TokenAssociation(google.protobuf.message.Message):
+ """A token - account association"""
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ TOKEN_ID_FIELD_NUMBER: builtins.int
+ ACCOUNT_ID_FIELD_NUMBER: builtins.int
+ @property
+ def token_id(self) -> global___TokenID:
+ """The token involved in the association"""
+ @property
+ def account_id(self) -> global___AccountID:
+ """The account involved in the association"""
+ def __init__(
+ self,
+ *,
+ token_id: global___TokenID | None = ...,
+ account_id: global___AccountID | None = ...,
+ ) -> None: ...
+ def HasField(self, field_name: typing_extensions.Literal["account_id", b"account_id", "token_id", b"token_id"]) -> builtins.bool: ...
+ def ClearField(self, field_name: typing_extensions.Literal["account_id", b"account_id", "token_id", b"token_id"]) -> None: ...
+
+global___TokenAssociation = TokenAssociation
+
+@typing_extensions.final
+class StakingInfo(google.protobuf.message.Message):
+ """*
+ Staking metadata for an account or a contract returned in CryptoGetInfo or
+ ContractGetInfo queries
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ DECLINE_REWARD_FIELD_NUMBER: builtins.int
+ STAKE_PERIOD_START_FIELD_NUMBER: builtins.int
+ PENDING_REWARD_FIELD_NUMBER: builtins.int
+ STAKED_TO_ME_FIELD_NUMBER: builtins.int
+ STAKED_ACCOUNT_ID_FIELD_NUMBER: builtins.int
+ STAKED_NODE_ID_FIELD_NUMBER: builtins.int
+ decline_reward: builtins.bool
+ """*
+ If true, this account or contract declined to receive a staking reward.
+ """
+ @property
+ def stake_period_start(self) -> proto.timestamp_pb2.Timestamp:
+ """*
+ The staking period during which either the staking settings for this
+ account or contract changed (such as starting staking or changing
+ staked_node_id) or the most recent reward was earned, whichever is later.
+ If this account or contract is not currently staked to a node, then this
+ field is not set.
+ """
+ pending_reward: builtins.int
+ """*
+ The amount in tinybars that will be received in the next reward situation.
+ """
+ staked_to_me: builtins.int
+ """*
+ The total of balance of all accounts staked to this account or contract.
+ """
+ @property
+ def staked_account_id(self) -> global___AccountID:
+ """*
+ The account to which this account or contract is staking.
+ """
+ staked_node_id: builtins.int
+ """*
+ The ID of the node this account or contract is staked to.
+ """
+ def __init__(
+ self,
+ *,
+ decline_reward: builtins.bool = ...,
+ stake_period_start: proto.timestamp_pb2.Timestamp | None = ...,
+ pending_reward: builtins.int = ...,
+ staked_to_me: builtins.int = ...,
+ staked_account_id: global___AccountID | None = ...,
+ staked_node_id: builtins.int = ...,
+ ) -> None: ...
+ def HasField(self, field_name: typing_extensions.Literal["stake_period_start", b"stake_period_start", "staked_account_id", b"staked_account_id", "staked_id", b"staked_id", "staked_node_id", b"staked_node_id"]) -> builtins.bool: ...
+ def ClearField(self, field_name: typing_extensions.Literal["decline_reward", b"decline_reward", "pending_reward", b"pending_reward", "stake_period_start", b"stake_period_start", "staked_account_id", b"staked_account_id", "staked_id", b"staked_id", "staked_node_id", b"staked_node_id", "staked_to_me", b"staked_to_me"]) -> None: ...
+ def WhichOneof(self, oneof_group: typing_extensions.Literal["staked_id", b"staked_id"]) -> typing_extensions.Literal["staked_account_id", "staked_node_id"] | None: ...
+
+global___StakingInfo = StakingInfo
diff --git a/proto/crypto_create.proto b/proto/crypto_create.proto
new file mode 100644
index 00000000..dbe42cec
--- /dev/null
+++ b/proto/crypto_create.proto
@@ -0,0 +1,160 @@
+syntax = "proto3";
+
+package Hedera;
+
+/*-
+ *
+ * Hedera Network Services Protobuf
+ *
+ * Copyright (C) 2018 - 2021 Hedera Hashgraph, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import "nanopb.proto";
+import "proto/basic_types.proto";
+import "proto/duration.proto";
+
+/*
+ * Create a new account. After the account is created, the AccountID for it is
+ * in the receipt. It can also be retrieved with a GetByKey query. Threshold
+ * values can be defined, and records are generated and stored for 25 hours for
+ * any transfer that exceeds the thresholds. This account is charged for each
+ * record generated, so the thresholds are useful for limiting record
+ * generation to happen only for large transactions.
+ *
+ * The Key field is the key used to sign transactions for this account. If the
+ * account has receiverSigRequired set to true, then all cryptocurrency
+ * transfers must be signed by this account's key, both for transfers in and
+ * out. If it is false, then only transfers out have to be signed by it. When
+ * the account is created, the payer account is charged enough hbars so that
+ * the new account will not expire for the next autoRenewPeriod seconds. When
+ * it reaches the expiration time, the new account will then be automatically
+ * charged to renew for another autoRenewPeriod seconds. If it does not have
+ * enough hbars to renew for that long, then the remaining hbars are used to
+ * extend its expiration as long as possible. If it is has a zero balance when
+ * it expires, then it is deleted. This transaction must be signed by the payer
+ * account. If receiverSigRequired is false, then the transaction does not have
+ * to be signed by the keys in the keys field. If it is true, then it must be
+ * signed by them, in addition to the keys of the payer account.
+ *
+ * An entity (account, file, or smart contract instance) must be created in a
+ * particular realm. If the realmID is left null, then a new realm will be
+ * created with the given admin key. If a new realm has a null adminKey, then
+ * anyone can create/modify/delete entities in that realm. But if an admin key
+ * is given, then any transaction to create/modify/delete an entity in that
+ * realm must be signed by that key, though anyone can still call functions on
+ * smart contract instances that exist in that realm. A realm ceases to exist
+ * when everything within it has expired and no longer exists.
+ *
+ * The current API ignores shardID, realmID, and newRealmAdminKey, and creates
+ * everything in shard 0 and realm 0, with a null key. Future versions of the
+ * API will support multiple realms and multiple shards.
+ */
+message CryptoCreateTransactionBody {
+ /**
+ * The key that must sign each transfer out of the account. If
+ * receiverSigRequired is true, then it must also sign any transfer into the
+ * account.
+ */
+ Key key = 1;
+
+ /**
+ * The initial number of tinybars to put into the account
+ */
+ uint64 initialBalance = 2;
+
+ /**
+ * [Deprecated] ID of the account to which this account is proxy staked. If
+ * proxyAccountID is null, or is an invalid account, or is an account that
+ * isn't a node, then this account is automatically proxy staked to a node
+ * chosen by the network, but without earning payments. If the proxyAccountID
+ * account refuses to accept proxy staking , or if it is not currently
+ * running a node, then it will behave as if proxyAccountID was null.
+ */
+ AccountID proxyAccountID = 3 [ deprecated = true ];
+
+ /**
+ * [Deprecated]. The threshold amount (in tinybars) for which an account
+ * record is created for any send/withdraw transaction
+ */
+ uint64 sendRecordThreshold = 6 [ deprecated = true ];
+
+ /**
+ * [Deprecated]. The threshold amount (in tinybars) for which an account
+ * record is created for any receive/deposit transaction
+ */
+ uint64 receiveRecordThreshold = 7 [ deprecated = true ];
+
+ /**
+ * If true, this account's key must sign any transaction depositing into this
+ * account (in addition to all withdrawals)
+ */
+ bool receiverSigRequired = 8;
+
+ /**
+ * The account is charged to extend its expiration date every this many
+ * seconds. If it doesn't have enough balance, it extends as long as
+ * possible. If it is empty when it expires, then it is deleted.
+ */
+ Duration autoRenewPeriod = 9;
+
+ /**
+ * The shard in which this account is created
+ */
+ ShardID shardID = 10;
+
+ /**
+ * The realm in which this account is created (leave this null to create a
+ * new realm)
+ */
+ RealmID realmID = 11;
+
+ /**
+ * If realmID is null, then this the admin key for the new realm that will be
+ * created
+ */
+ Key newRealmAdminKey = 12;
+
+ /**
+ * The memo associated with the account (UTF-8 encoding max 100 bytes)
+ */
+ string memo = 13 [ (nanopb).max_size = 100 ];
+
+ /**
+ * The maximum number of tokens that an Account can be implicitly associated
+ * with. Defaults to 0 and up to a maximum value of 1000.
+ */
+ int32 max_automatic_token_associations = 14;
+
+ /**
+ * ID of the account or node to which this account is staking.
+ */
+ oneof staked_id {
+ /**
+ * ID of the account to which this account is staking.
+ */
+ AccountID staked_account_id = 15;
+
+ /**
+ * ID of the node this account is staked to.
+ */
+ int64 staked_node_id = 16;
+ }
+
+ /**
+ * If true, the account declines receiving a staking reward. The default
+ * value is false.
+ */
+ bool decline_reward = 17;
+}
\ No newline at end of file
diff --git a/proto/crypto_create_pb2.py b/proto/crypto_create_pb2.py
new file mode 100644
index 00000000..6f2009d0
--- /dev/null
+++ b/proto/crypto_create_pb2.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: proto/crypto_create.proto
+"""Generated protocol buffer code."""
+from google.protobuf.internal import builder as _builder
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+import nanopb_pb2 as nanopb__pb2
+from proto import basic_types_pb2 as proto_dot_basic__types__pb2
+from proto import duration_pb2 as proto_dot_duration__pb2
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x19proto/crypto_create.proto\x12\x06Hedera\x1a\x0cnanopb.proto\x1a\x17proto/basic_types.proto\x1a\x14proto/duration.proto\"\xa4\x04\n\x1b\x43ryptoCreateTransactionBody\x12\x18\n\x03key\x18\x01 \x01(\x0b\x32\x0b.Hedera.Key\x12\x16\n\x0einitialBalance\x18\x02 \x01(\x04\x12-\n\x0eproxyAccountID\x18\x03 \x01(\x0b\x32\x11.Hedera.AccountIDB\x02\x18\x01\x12\x1f\n\x13sendRecordThreshold\x18\x06 \x01(\x04\x42\x02\x18\x01\x12\"\n\x16receiveRecordThreshold\x18\x07 \x01(\x04\x42\x02\x18\x01\x12\x1b\n\x13receiverSigRequired\x18\x08 \x01(\x08\x12)\n\x0f\x61utoRenewPeriod\x18\t \x01(\x0b\x32\x10.Hedera.Duration\x12 \n\x07shardID\x18\n \x01(\x0b\x32\x0f.Hedera.ShardID\x12 \n\x07realmID\x18\x0b \x01(\x0b\x32\x0f.Hedera.RealmID\x12%\n\x10newRealmAdminKey\x18\x0c \x01(\x0b\x32\x0b.Hedera.Key\x12\x13\n\x04memo\x18\r \x01(\tB\x05\x92?\x02\x08\x64\x12(\n max_automatic_token_associations\x18\x0e \x01(\x05\x12.\n\x11staked_account_id\x18\x0f \x01(\x0b\x32\x11.Hedera.AccountIDH\x00\x12\x18\n\x0estaked_node_id\x18\x10 \x01(\x03H\x00\x12\x16\n\x0e\x64\x65\x63line_reward\x18\x11 \x01(\x08\x42\x0b\n\tstaked_idb\x06proto3')
+
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'proto.crypto_create_pb2', globals())
+if _descriptor._USE_C_DESCRIPTORS == False:
+
+ DESCRIPTOR._options = None
+ _CRYPTOCREATETRANSACTIONBODY.fields_by_name['proxyAccountID']._options = None
+ _CRYPTOCREATETRANSACTIONBODY.fields_by_name['proxyAccountID']._serialized_options = b'\030\001'
+ _CRYPTOCREATETRANSACTIONBODY.fields_by_name['sendRecordThreshold']._options = None
+ _CRYPTOCREATETRANSACTIONBODY.fields_by_name['sendRecordThreshold']._serialized_options = b'\030\001'
+ _CRYPTOCREATETRANSACTIONBODY.fields_by_name['receiveRecordThreshold']._options = None
+ _CRYPTOCREATETRANSACTIONBODY.fields_by_name['receiveRecordThreshold']._serialized_options = b'\030\001'
+ _CRYPTOCREATETRANSACTIONBODY.fields_by_name['memo']._options = None
+ _CRYPTOCREATETRANSACTIONBODY.fields_by_name['memo']._serialized_options = b'\222?\002\010d'
+ _CRYPTOCREATETRANSACTIONBODY._serialized_start=99
+ _CRYPTOCREATETRANSACTIONBODY._serialized_end=647
+# @@protoc_insertion_point(module_scope)
diff --git a/proto/crypto_create_pb2.pyi b/proto/crypto_create_pb2.pyi
new file mode 100644
index 00000000..2d472669
--- /dev/null
+++ b/proto/crypto_create_pb2.pyi
@@ -0,0 +1,181 @@
+"""
+@generated by mypy-protobuf. Do not edit manually!
+isort:skip_file
+"""
+import builtins
+import google.protobuf.descriptor
+import google.protobuf.message
+import proto.basic_types_pb2
+import proto.duration_pb2
+import sys
+
+if sys.version_info >= (3, 8):
+ import typing as typing_extensions
+else:
+ import typing_extensions
+
+DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
+
+@typing_extensions.final
+class CryptoCreateTransactionBody(google.protobuf.message.Message):
+ """
+ Create a new account. After the account is created, the AccountID for it is
+ in the receipt. It can also be retrieved with a GetByKey query. Threshold
+ values can be defined, and records are generated and stored for 25 hours for
+ any transfer that exceeds the thresholds. This account is charged for each
+ record generated, so the thresholds are useful for limiting record
+ generation to happen only for large transactions.
+
+ The Key field is the key used to sign transactions for this account. If the
+ account has receiverSigRequired set to true, then all cryptocurrency
+ transfers must be signed by this account's key, both for transfers in and
+ out. If it is false, then only transfers out have to be signed by it. When
+ the account is created, the payer account is charged enough hbars so that
+ the new account will not expire for the next autoRenewPeriod seconds. When
+ it reaches the expiration time, the new account will then be automatically
+ charged to renew for another autoRenewPeriod seconds. If it does not have
+ enough hbars to renew for that long, then the remaining hbars are used to
+ extend its expiration as long as possible. If it is has a zero balance when
+ it expires, then it is deleted. This transaction must be signed by the payer
+ account. If receiverSigRequired is false, then the transaction does not have
+ to be signed by the keys in the keys field. If it is true, then it must be
+ signed by them, in addition to the keys of the payer account.
+
+ An entity (account, file, or smart contract instance) must be created in a
+ particular realm. If the realmID is left null, then a new realm will be
+ created with the given admin key. If a new realm has a null adminKey, then
+ anyone can create/modify/delete entities in that realm. But if an admin key
+ is given, then any transaction to create/modify/delete an entity in that
+ realm must be signed by that key, though anyone can still call functions on
+ smart contract instances that exist in that realm. A realm ceases to exist
+ when everything within it has expired and no longer exists.
+
+ The current API ignores shardID, realmID, and newRealmAdminKey, and creates
+ everything in shard 0 and realm 0, with a null key. Future versions of the
+ API will support multiple realms and multiple shards.
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ KEY_FIELD_NUMBER: builtins.int
+ INITIALBALANCE_FIELD_NUMBER: builtins.int
+ PROXYACCOUNTID_FIELD_NUMBER: builtins.int
+ SENDRECORDTHRESHOLD_FIELD_NUMBER: builtins.int
+ RECEIVERECORDTHRESHOLD_FIELD_NUMBER: builtins.int
+ RECEIVERSIGREQUIRED_FIELD_NUMBER: builtins.int
+ AUTORENEWPERIOD_FIELD_NUMBER: builtins.int
+ SHARDID_FIELD_NUMBER: builtins.int
+ REALMID_FIELD_NUMBER: builtins.int
+ NEWREALMADMINKEY_FIELD_NUMBER: builtins.int
+ MEMO_FIELD_NUMBER: builtins.int
+ MAX_AUTOMATIC_TOKEN_ASSOCIATIONS_FIELD_NUMBER: builtins.int
+ STAKED_ACCOUNT_ID_FIELD_NUMBER: builtins.int
+ STAKED_NODE_ID_FIELD_NUMBER: builtins.int
+ DECLINE_REWARD_FIELD_NUMBER: builtins.int
+ @property
+ def key(self) -> proto.basic_types_pb2.Key:
+ """*
+ The key that must sign each transfer out of the account. If
+ receiverSigRequired is true, then it must also sign any transfer into the
+ account.
+ """
+ initialBalance: builtins.int
+ """*
+ The initial number of tinybars to put into the account
+ """
+ @property
+ def proxyAccountID(self) -> proto.basic_types_pb2.AccountID:
+ """*
+ [Deprecated] ID of the account to which this account is proxy staked. If
+ proxyAccountID is null, or is an invalid account, or is an account that
+ isn't a node, then this account is automatically proxy staked to a node
+ chosen by the network, but without earning payments. If the proxyAccountID
+ account refuses to accept proxy staking , or if it is not currently
+ running a node, then it will behave as if proxyAccountID was null.
+ """
+ sendRecordThreshold: builtins.int
+ """*
+ [Deprecated]. The threshold amount (in tinybars) for which an account
+ record is created for any send/withdraw transaction
+ """
+ receiveRecordThreshold: builtins.int
+ """*
+ [Deprecated]. The threshold amount (in tinybars) for which an account
+ record is created for any receive/deposit transaction
+ """
+ receiverSigRequired: builtins.bool
+ """*
+ If true, this account's key must sign any transaction depositing into this
+ account (in addition to all withdrawals)
+ """
+ @property
+ def autoRenewPeriod(self) -> proto.duration_pb2.Duration:
+ """*
+ The account is charged to extend its expiration date every this many
+ seconds. If it doesn't have enough balance, it extends as long as
+ possible. If it is empty when it expires, then it is deleted.
+ """
+ @property
+ def shardID(self) -> proto.basic_types_pb2.ShardID:
+ """*
+ The shard in which this account is created
+ """
+ @property
+ def realmID(self) -> proto.basic_types_pb2.RealmID:
+ """*
+ The realm in which this account is created (leave this null to create a
+ new realm)
+ """
+ @property
+ def newRealmAdminKey(self) -> proto.basic_types_pb2.Key:
+ """*
+ If realmID is null, then this the admin key for the new realm that will be
+ created
+ """
+ memo: builtins.str
+ """*
+ The memo associated with the account (UTF-8 encoding max 100 bytes)
+ """
+ max_automatic_token_associations: builtins.int
+ """*
+ The maximum number of tokens that an Account can be implicitly associated
+ with. Defaults to 0 and up to a maximum value of 1000.
+ """
+ @property
+ def staked_account_id(self) -> proto.basic_types_pb2.AccountID:
+ """*
+ ID of the account to which this account is staking.
+ """
+ staked_node_id: builtins.int
+ """*
+ ID of the node this account is staked to.
+ """
+ decline_reward: builtins.bool
+ """*
+ If true, the account declines receiving a staking reward. The default
+ value is false.
+ """
+ def __init__(
+ self,
+ *,
+ key: proto.basic_types_pb2.Key | None = ...,
+ initialBalance: builtins.int = ...,
+ proxyAccountID: proto.basic_types_pb2.AccountID | None = ...,
+ sendRecordThreshold: builtins.int = ...,
+ receiveRecordThreshold: builtins.int = ...,
+ receiverSigRequired: builtins.bool = ...,
+ autoRenewPeriod: proto.duration_pb2.Duration | None = ...,
+ shardID: proto.basic_types_pb2.ShardID | None = ...,
+ realmID: proto.basic_types_pb2.RealmID | None = ...,
+ newRealmAdminKey: proto.basic_types_pb2.Key | None = ...,
+ memo: builtins.str = ...,
+ max_automatic_token_associations: builtins.int = ...,
+ staked_account_id: proto.basic_types_pb2.AccountID | None = ...,
+ staked_node_id: builtins.int = ...,
+ decline_reward: builtins.bool = ...,
+ ) -> None: ...
+ def HasField(self, field_name: typing_extensions.Literal["autoRenewPeriod", b"autoRenewPeriod", "key", b"key", "newRealmAdminKey", b"newRealmAdminKey", "proxyAccountID", b"proxyAccountID", "realmID", b"realmID", "shardID", b"shardID", "staked_account_id", b"staked_account_id", "staked_id", b"staked_id", "staked_node_id", b"staked_node_id"]) -> builtins.bool: ...
+ def ClearField(self, field_name: typing_extensions.Literal["autoRenewPeriod", b"autoRenewPeriod", "decline_reward", b"decline_reward", "initialBalance", b"initialBalance", "key", b"key", "max_automatic_token_associations", b"max_automatic_token_associations", "memo", b"memo", "newRealmAdminKey", b"newRealmAdminKey", "proxyAccountID", b"proxyAccountID", "realmID", b"realmID", "receiveRecordThreshold", b"receiveRecordThreshold", "receiverSigRequired", b"receiverSigRequired", "sendRecordThreshold", b"sendRecordThreshold", "shardID", b"shardID", "staked_account_id", b"staked_account_id", "staked_id", b"staked_id", "staked_node_id", b"staked_node_id"]) -> None: ...
+ def WhichOneof(self, oneof_group: typing_extensions.Literal["staked_id", b"staked_id"]) -> typing_extensions.Literal["staked_account_id", "staked_node_id"] | None: ...
+
+global___CryptoCreateTransactionBody = CryptoCreateTransactionBody
diff --git a/proto/crypto_transfer.proto b/proto/crypto_transfer.proto
new file mode 100644
index 00000000..9b59bf3d
--- /dev/null
+++ b/proto/crypto_transfer.proto
@@ -0,0 +1,55 @@
+syntax = "proto3";
+
+package Hedera;
+
+/*-
+ *
+ * Hedera Network Services Protobuf
+ *
+ * Copyright (C) 2018 - 2021 Hedera Hashgraph, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import "nanopb.proto";
+import "proto/basic_types.proto";
+
+/**
+ * Transfers cryptocurrency among two or more accounts by making the desired
+ * adjustments to their balances. Each transfer list can specify up to 10
+ * adjustments. Each negative amount is withdrawn from the corresponding
+ * account (a sender), and each positive one is added to the corresponding
+ * account (a receiver). The amounts list must sum to zero. Each amount is a
+ * number of tinybars (there are 100,000,000 tinybars in one hbar). If any
+ * sender account fails to have sufficient hbars, then the entire transaction
+ * fails, and none of those transfers occur, though the transaction fee is
+ * still charged. This transaction must be signed by the keys for all the
+ * sending accounts, and for any receiving accounts that have
+ * receiverSigRequired == true. The signatures are in the same order as the
+ * accounts, skipping those accounts that don't need a signature.
+ */
+message CryptoTransferTransactionBody {
+ /**
+ * The desired hbar balance adjustments
+ */
+ TransferList transfers = 1;
+
+ /**
+ * The desired token unit balance adjustments; if any custom fees are
+ * assessed, the ledger will try to deduct them from the payer of this
+ * CryptoTransfer, resolving the transaction to
+ * INSUFFICIENT_PAYER_BALANCE_FOR_CUSTOM_FEE if this is not possible
+ * Limited to 1 here
+ */
+ repeated TokenTransferList tokenTransfers = 2 [ (nanopb).max_count = 1 ];
+}
\ No newline at end of file
diff --git a/proto/crypto_transfer_pb2.py b/proto/crypto_transfer_pb2.py
new file mode 100644
index 00000000..9c40c036
--- /dev/null
+++ b/proto/crypto_transfer_pb2.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: proto/crypto_transfer.proto
+"""Generated protocol buffer code."""
+from google.protobuf.internal import builder as _builder
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+import nanopb_pb2 as nanopb__pb2
+from proto import basic_types_pb2 as proto_dot_basic__types__pb2
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1bproto/crypto_transfer.proto\x12\x06Hedera\x1a\x0cnanopb.proto\x1a\x17proto/basic_types.proto\"\x82\x01\n\x1d\x43ryptoTransferTransactionBody\x12\'\n\ttransfers\x18\x01 \x01(\x0b\x32\x14.Hedera.TransferList\x12\x38\n\x0etokenTransfers\x18\x02 \x03(\x0b\x32\x19.Hedera.TokenTransferListB\x05\x92?\x02\x10\x01\x62\x06proto3')
+
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'proto.crypto_transfer_pb2', globals())
+if _descriptor._USE_C_DESCRIPTORS == False:
+
+ DESCRIPTOR._options = None
+ _CRYPTOTRANSFERTRANSACTIONBODY.fields_by_name['tokenTransfers']._options = None
+ _CRYPTOTRANSFERTRANSACTIONBODY.fields_by_name['tokenTransfers']._serialized_options = b'\222?\002\020\001'
+ _CRYPTOTRANSFERTRANSACTIONBODY._serialized_start=79
+ _CRYPTOTRANSFERTRANSACTIONBODY._serialized_end=209
+# @@protoc_insertion_point(module_scope)
diff --git a/proto/crypto_transfer_pb2.pyi b/proto/crypto_transfer_pb2.pyi
new file mode 100644
index 00000000..831e8333
--- /dev/null
+++ b/proto/crypto_transfer_pb2.pyi
@@ -0,0 +1,64 @@
+"""
+@generated by mypy-protobuf. Do not edit manually!
+isort:skip_file
+"""
+import builtins
+import collections.abc
+import google.protobuf.descriptor
+import google.protobuf.internal.containers
+import google.protobuf.message
+import proto.basic_types_pb2
+import sys
+
+if sys.version_info >= (3, 8):
+ import typing as typing_extensions
+else:
+ import typing_extensions
+
+DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
+
+@typing_extensions.final
+class CryptoTransferTransactionBody(google.protobuf.message.Message):
+ """*
+ Transfers cryptocurrency among two or more accounts by making the desired
+ adjustments to their balances. Each transfer list can specify up to 10
+ adjustments. Each negative amount is withdrawn from the corresponding
+ account (a sender), and each positive one is added to the corresponding
+ account (a receiver). The amounts list must sum to zero. Each amount is a
+ number of tinybars (there are 100,000,000 tinybars in one hbar). If any
+ sender account fails to have sufficient hbars, then the entire transaction
+ fails, and none of those transfers occur, though the transaction fee is
+ still charged. This transaction must be signed by the keys for all the
+ sending accounts, and for any receiving accounts that have
+ receiverSigRequired == true. The signatures are in the same order as the
+ accounts, skipping those accounts that don't need a signature.
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ TRANSFERS_FIELD_NUMBER: builtins.int
+ TOKENTRANSFERS_FIELD_NUMBER: builtins.int
+ @property
+ def transfers(self) -> proto.basic_types_pb2.TransferList:
+ """*
+ The desired hbar balance adjustments
+ """
+ @property
+ def tokenTransfers(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[proto.basic_types_pb2.TokenTransferList]:
+ """*
+ The desired token unit balance adjustments; if any custom fees are
+ assessed, the ledger will try to deduct them from the payer of this
+ CryptoTransfer, resolving the transaction to
+ INSUFFICIENT_PAYER_BALANCE_FOR_CUSTOM_FEE if this is not possible
+ Limited to 1 here
+ """
+ def __init__(
+ self,
+ *,
+ transfers: proto.basic_types_pb2.TransferList | None = ...,
+ tokenTransfers: collections.abc.Iterable[proto.basic_types_pb2.TokenTransferList] | None = ...,
+ ) -> None: ...
+ def HasField(self, field_name: typing_extensions.Literal["transfers", b"transfers"]) -> builtins.bool: ...
+ def ClearField(self, field_name: typing_extensions.Literal["tokenTransfers", b"tokenTransfers", "transfers", b"transfers"]) -> None: ...
+
+global___CryptoTransferTransactionBody = CryptoTransferTransactionBody
diff --git a/proto/crypto_update.proto b/proto/crypto_update.proto
new file mode 100644
index 00000000..a60c27bc
--- /dev/null
+++ b/proto/crypto_update.proto
@@ -0,0 +1,157 @@
+syntax = "proto3";
+
+package Hedera;
+
+/*-
+ *
+ * Hedera Network Services Protobuf
+ *
+ * Copyright (C) 2018 - 2021 Hedera Hashgraph, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import "nanopb.proto";
+import "proto/basic_types.proto";
+import "proto/duration.proto";
+import "proto/timestamp.proto";
+import "proto/wrappers.proto";
+
+/**
+ * Change properties for the given account. Any null field is ignored (left
+ * unchanged). This transaction must be signed by the existing key for this
+ * account. If the transaction is changing the key field, then the transaction
+ * must be signed by both the old key (from before the change) and the new key.
+ * The old key must sign for security. The new key must sign as a safeguard to
+ * avoid accidentally changing to an invalid key, and then having no way to
+ * recover.
+ */
+message CryptoUpdateTransactionBody {
+ /**
+ * The account ID which is being updated in this transaction
+ */
+ AccountID accountIDToUpdate = 2;
+
+ /**
+ * The new key
+ */
+ Key key = 3;
+
+ /**
+ * [Deprecated] ID of the account to which this account is proxy staked. If
+ * proxyAccountID is null, or is an invalid account, or is an account that
+ * isn't a node, then this account is automatically proxy staked to a node
+ * chosen by the network, but without earning payments. If the proxyAccountID
+ * account refuses to accept proxy staking , or if it is not currently running
+ * a node, then it will behave as if proxyAccountID was null.
+ */
+ AccountID proxyAccountID = 4 [ deprecated = true ];
+
+ /**
+ * [Deprecated]. Payments earned from proxy staking are shared between the
+ * node and this account, with proxyFraction / 10000 going to this account
+ */
+ int32 proxyFraction = 5 [ deprecated = true ];
+
+ oneof sendRecordThresholdField {
+ /**
+ * [Deprecated]. The new threshold amount (in tinybars) for which an account
+ * record is created for any send/withdraw transaction
+ */
+ uint64 sendRecordThreshold = 6 [ deprecated = true ];
+
+ /**
+ * [Deprecated]. The new threshold amount (in tinybars) for which an account
+ * record is created for any send/withdraw transaction
+ */
+ UInt64Value sendRecordThresholdWrapper = 11 [ deprecated = true ];
+ }
+
+ oneof receiveRecordThresholdField {
+ /**
+ * [Deprecated]. The new threshold amount (in tinybars) for which an account
+ * record is created for any receive/deposit transaction.
+ */
+ uint64 receiveRecordThreshold = 7 [ deprecated = true ];
+
+ /**
+ * [Deprecated]. The new threshold amount (in tinybars) for which an account
+ * record is created for any receive/deposit transaction.
+ */
+ UInt64Value receiveRecordThresholdWrapper = 12 [ deprecated = true ];
+ }
+
+ /**
+ * The duration in which it will automatically extend the expiration period.
+ * If it doesn't have enough balance, it extends as long as possible. If it is
+ * empty when it expires, then it is deleted.
+ */
+ Duration autoRenewPeriod = 8;
+
+ /**
+ * The new expiration time to extend to (ignored if equal to or before the
+ * current one)
+ */
+ Timestamp expirationTime = 9;
+
+ oneof receiverSigRequiredField {
+ /**
+ * [Deprecated] Do NOT use this field to set a false value because the
+ * server cannot distinguish from the default value. Use
+ * receiverSigRequiredWrapper field for this purpose.
+ */
+ bool receiverSigRequired = 10 [ deprecated = true ];
+
+ /**
+ * If true, this account's key must sign any transaction depositing into
+ * this account (in addition to all withdrawals)
+ */
+ BoolValue receiverSigRequiredWrapper = 13;
+ }
+
+ /**
+ * If set, the new memo to be associated with the account (UTF-8 encoding max
+ * 100 bytes)
+ */
+ StringValue memo = 14;
+
+ /**
+ * The maximum number of tokens that an Account can be implicitly associated
+ * with. Up to a 1000 including implicit and explicit associations.
+ */
+ Int32Value max_automatic_token_associations = 15;
+
+ /**
+ * ID of the account or node to which this account is staking.
+ */
+ oneof staked_id {
+ /**
+ * ID of the new account to which this account is staking. If set to the
+ * sentinel 0.0.0 AccountID, this field removes this account's
+ * staked account ID.
+ */
+ AccountID staked_account_id = 16;
+
+ /**
+ * ID of the new node this account is staked to. If set to the sentinel
+ * -1, this field removes this account's staked node ID.
+ */
+ int64 staked_node_id = 17;
+ }
+
+ /**
+ * If true, the account declines receiving a staking reward. The default value
+ * is false.
+ */
+ BoolValue decline_reward = 18;
+}
\ No newline at end of file
diff --git a/proto/crypto_update_pb2.py b/proto/crypto_update_pb2.py
new file mode 100644
index 00000000..1936fb2f
--- /dev/null
+++ b/proto/crypto_update_pb2.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: proto/crypto_update.proto
+"""Generated protocol buffer code."""
+from google.protobuf.internal import builder as _builder
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+import nanopb_pb2 as nanopb__pb2
+from proto import basic_types_pb2 as proto_dot_basic__types__pb2
+from proto import duration_pb2 as proto_dot_duration__pb2
+from proto import timestamp_pb2 as proto_dot_timestamp__pb2
+from proto import wrappers_pb2 as proto_dot_wrappers__pb2
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x19proto/crypto_update.proto\x12\x06Hedera\x1a\x0cnanopb.proto\x1a\x17proto/basic_types.proto\x1a\x14proto/duration.proto\x1a\x15proto/timestamp.proto\x1a\x14proto/wrappers.proto\"\xe5\x06\n\x1b\x43ryptoUpdateTransactionBody\x12,\n\x11\x61\x63\x63ountIDToUpdate\x18\x02 \x01(\x0b\x32\x11.Hedera.AccountID\x12\x18\n\x03key\x18\x03 \x01(\x0b\x32\x0b.Hedera.Key\x12-\n\x0eproxyAccountID\x18\x04 \x01(\x0b\x32\x11.Hedera.AccountIDB\x02\x18\x01\x12\x19\n\rproxyFraction\x18\x05 \x01(\x05\x42\x02\x18\x01\x12!\n\x13sendRecordThreshold\x18\x06 \x01(\x04\x42\x02\x18\x01H\x00\x12=\n\x1asendRecordThresholdWrapper\x18\x0b \x01(\x0b\x32\x13.Hedera.UInt64ValueB\x02\x18\x01H\x00\x12$\n\x16receiveRecordThreshold\x18\x07 \x01(\x04\x42\x02\x18\x01H\x01\x12@\n\x1dreceiveRecordThresholdWrapper\x18\x0c \x01(\x0b\x32\x13.Hedera.UInt64ValueB\x02\x18\x01H\x01\x12)\n\x0f\x61utoRenewPeriod\x18\x08 \x01(\x0b\x32\x10.Hedera.Duration\x12)\n\x0e\x65xpirationTime\x18\t \x01(\x0b\x32\x11.Hedera.Timestamp\x12!\n\x13receiverSigRequired\x18\n \x01(\x08\x42\x02\x18\x01H\x02\x12\x37\n\x1areceiverSigRequiredWrapper\x18\r \x01(\x0b\x32\x11.Hedera.BoolValueH\x02\x12!\n\x04memo\x18\x0e \x01(\x0b\x32\x13.Hedera.StringValue\x12<\n max_automatic_token_associations\x18\x0f \x01(\x0b\x32\x12.Hedera.Int32Value\x12.\n\x11staked_account_id\x18\x10 \x01(\x0b\x32\x11.Hedera.AccountIDH\x03\x12\x18\n\x0estaked_node_id\x18\x11 \x01(\x03H\x03\x12)\n\x0e\x64\x65\x63line_reward\x18\x12 \x01(\x0b\x32\x11.Hedera.BoolValueB\x1a\n\x18sendRecordThresholdFieldB\x1d\n\x1breceiveRecordThresholdFieldB\x1a\n\x18receiverSigRequiredFieldB\x0b\n\tstaked_idb\x06proto3')
+
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'proto.crypto_update_pb2', globals())
+if _descriptor._USE_C_DESCRIPTORS == False:
+
+ DESCRIPTOR._options = None
+ _CRYPTOUPDATETRANSACTIONBODY.fields_by_name['proxyAccountID']._options = None
+ _CRYPTOUPDATETRANSACTIONBODY.fields_by_name['proxyAccountID']._serialized_options = b'\030\001'
+ _CRYPTOUPDATETRANSACTIONBODY.fields_by_name['proxyFraction']._options = None
+ _CRYPTOUPDATETRANSACTIONBODY.fields_by_name['proxyFraction']._serialized_options = b'\030\001'
+ _CRYPTOUPDATETRANSACTIONBODY.fields_by_name['sendRecordThreshold']._options = None
+ _CRYPTOUPDATETRANSACTIONBODY.fields_by_name['sendRecordThreshold']._serialized_options = b'\030\001'
+ _CRYPTOUPDATETRANSACTIONBODY.fields_by_name['sendRecordThresholdWrapper']._options = None
+ _CRYPTOUPDATETRANSACTIONBODY.fields_by_name['sendRecordThresholdWrapper']._serialized_options = b'\030\001'
+ _CRYPTOUPDATETRANSACTIONBODY.fields_by_name['receiveRecordThreshold']._options = None
+ _CRYPTOUPDATETRANSACTIONBODY.fields_by_name['receiveRecordThreshold']._serialized_options = b'\030\001'
+ _CRYPTOUPDATETRANSACTIONBODY.fields_by_name['receiveRecordThresholdWrapper']._options = None
+ _CRYPTOUPDATETRANSACTIONBODY.fields_by_name['receiveRecordThresholdWrapper']._serialized_options = b'\030\001'
+ _CRYPTOUPDATETRANSACTIONBODY.fields_by_name['receiverSigRequired']._options = None
+ _CRYPTOUPDATETRANSACTIONBODY.fields_by_name['receiverSigRequired']._serialized_options = b'\030\001'
+ _CRYPTOUPDATETRANSACTIONBODY._serialized_start=144
+ _CRYPTOUPDATETRANSACTIONBODY._serialized_end=1013
+# @@protoc_insertion_point(module_scope)
diff --git a/proto/crypto_update_pb2.pyi b/proto/crypto_update_pb2.pyi
new file mode 100644
index 00000000..e1ca7bc6
--- /dev/null
+++ b/proto/crypto_update_pb2.pyi
@@ -0,0 +1,187 @@
+"""
+@generated by mypy-protobuf. Do not edit manually!
+isort:skip_file
+"""
+import builtins
+import google.protobuf.descriptor
+import google.protobuf.message
+import proto.basic_types_pb2
+import proto.duration_pb2
+import proto.timestamp_pb2
+import proto.wrappers_pb2
+import sys
+import typing
+
+if sys.version_info >= (3, 8):
+ import typing as typing_extensions
+else:
+ import typing_extensions
+
+DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
+
+@typing_extensions.final
+class CryptoUpdateTransactionBody(google.protobuf.message.Message):
+ """*
+ Change properties for the given account. Any null field is ignored (left
+ unchanged). This transaction must be signed by the existing key for this
+ account. If the transaction is changing the key field, then the transaction
+ must be signed by both the old key (from before the change) and the new key.
+ The old key must sign for security. The new key must sign as a safeguard to
+ avoid accidentally changing to an invalid key, and then having no way to
+ recover.
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ ACCOUNTIDTOUPDATE_FIELD_NUMBER: builtins.int
+ KEY_FIELD_NUMBER: builtins.int
+ PROXYACCOUNTID_FIELD_NUMBER: builtins.int
+ PROXYFRACTION_FIELD_NUMBER: builtins.int
+ SENDRECORDTHRESHOLD_FIELD_NUMBER: builtins.int
+ SENDRECORDTHRESHOLDWRAPPER_FIELD_NUMBER: builtins.int
+ RECEIVERECORDTHRESHOLD_FIELD_NUMBER: builtins.int
+ RECEIVERECORDTHRESHOLDWRAPPER_FIELD_NUMBER: builtins.int
+ AUTORENEWPERIOD_FIELD_NUMBER: builtins.int
+ EXPIRATIONTIME_FIELD_NUMBER: builtins.int
+ RECEIVERSIGREQUIRED_FIELD_NUMBER: builtins.int
+ RECEIVERSIGREQUIREDWRAPPER_FIELD_NUMBER: builtins.int
+ MEMO_FIELD_NUMBER: builtins.int
+ MAX_AUTOMATIC_TOKEN_ASSOCIATIONS_FIELD_NUMBER: builtins.int
+ STAKED_ACCOUNT_ID_FIELD_NUMBER: builtins.int
+ STAKED_NODE_ID_FIELD_NUMBER: builtins.int
+ DECLINE_REWARD_FIELD_NUMBER: builtins.int
+ @property
+ def accountIDToUpdate(self) -> proto.basic_types_pb2.AccountID:
+ """*
+ The account ID which is being updated in this transaction
+ """
+ @property
+ def key(self) -> proto.basic_types_pb2.Key:
+ """*
+ The new key
+ """
+ @property
+ def proxyAccountID(self) -> proto.basic_types_pb2.AccountID:
+ """*
+ [Deprecated] ID of the account to which this account is proxy staked. If
+ proxyAccountID is null, or is an invalid account, or is an account that
+ isn't a node, then this account is automatically proxy staked to a node
+ chosen by the network, but without earning payments. If the proxyAccountID
+ account refuses to accept proxy staking , or if it is not currently running
+ a node, then it will behave as if proxyAccountID was null.
+ """
+ proxyFraction: builtins.int
+ """*
+ [Deprecated]. Payments earned from proxy staking are shared between the
+ node and this account, with proxyFraction / 10000 going to this account
+ """
+ sendRecordThreshold: builtins.int
+ """*
+ [Deprecated]. The new threshold amount (in tinybars) for which an account
+ record is created for any send/withdraw transaction
+ """
+ @property
+ def sendRecordThresholdWrapper(self) -> proto.wrappers_pb2.UInt64Value:
+ """*
+ [Deprecated]. The new threshold amount (in tinybars) for which an account
+ record is created for any send/withdraw transaction
+ """
+ receiveRecordThreshold: builtins.int
+ """*
+ [Deprecated]. The new threshold amount (in tinybars) for which an account
+ record is created for any receive/deposit transaction.
+ """
+ @property
+ def receiveRecordThresholdWrapper(self) -> proto.wrappers_pb2.UInt64Value:
+ """*
+ [Deprecated]. The new threshold amount (in tinybars) for which an account
+ record is created for any receive/deposit transaction.
+ """
+ @property
+ def autoRenewPeriod(self) -> proto.duration_pb2.Duration:
+ """*
+ The duration in which it will automatically extend the expiration period.
+ If it doesn't have enough balance, it extends as long as possible. If it is
+ empty when it expires, then it is deleted.
+ """
+ @property
+ def expirationTime(self) -> proto.timestamp_pb2.Timestamp:
+ """*
+ The new expiration time to extend to (ignored if equal to or before the
+ current one)
+ """
+ receiverSigRequired: builtins.bool
+ """*
+ [Deprecated] Do NOT use this field to set a false value because the
+ server cannot distinguish from the default value. Use
+ receiverSigRequiredWrapper field for this purpose.
+ """
+ @property
+ def receiverSigRequiredWrapper(self) -> proto.wrappers_pb2.BoolValue:
+ """*
+ If true, this account's key must sign any transaction depositing into
+ this account (in addition to all withdrawals)
+ """
+ @property
+ def memo(self) -> proto.wrappers_pb2.StringValue:
+ """*
+ If set, the new memo to be associated with the account (UTF-8 encoding max
+ 100 bytes)
+ """
+ @property
+ def max_automatic_token_associations(self) -> proto.wrappers_pb2.Int32Value:
+ """*
+ The maximum number of tokens that an Account can be implicitly associated
+ with. Up to a 1000 including implicit and explicit associations.
+ """
+ @property
+ def staked_account_id(self) -> proto.basic_types_pb2.AccountID:
+ """*
+ ID of the new account to which this account is staking. If set to the
+ sentinel 0.0.0 AccountID, this field removes this account's
+ staked account ID.
+ """
+ staked_node_id: builtins.int
+ """*
+ ID of the new node this account is staked to. If set to the sentinel
+ -1, this field removes this account's staked node ID.
+ """
+ @property
+ def decline_reward(self) -> proto.wrappers_pb2.BoolValue:
+ """*
+ If true, the account declines receiving a staking reward. The default value
+ is false.
+ """
+ def __init__(
+ self,
+ *,
+ accountIDToUpdate: proto.basic_types_pb2.AccountID | None = ...,
+ key: proto.basic_types_pb2.Key | None = ...,
+ proxyAccountID: proto.basic_types_pb2.AccountID | None = ...,
+ proxyFraction: builtins.int = ...,
+ sendRecordThreshold: builtins.int = ...,
+ sendRecordThresholdWrapper: proto.wrappers_pb2.UInt64Value | None = ...,
+ receiveRecordThreshold: builtins.int = ...,
+ receiveRecordThresholdWrapper: proto.wrappers_pb2.UInt64Value | None = ...,
+ autoRenewPeriod: proto.duration_pb2.Duration | None = ...,
+ expirationTime: proto.timestamp_pb2.Timestamp | None = ...,
+ receiverSigRequired: builtins.bool = ...,
+ receiverSigRequiredWrapper: proto.wrappers_pb2.BoolValue | None = ...,
+ memo: proto.wrappers_pb2.StringValue | None = ...,
+ max_automatic_token_associations: proto.wrappers_pb2.Int32Value | None = ...,
+ staked_account_id: proto.basic_types_pb2.AccountID | None = ...,
+ staked_node_id: builtins.int = ...,
+ decline_reward: proto.wrappers_pb2.BoolValue | None = ...,
+ ) -> None: ...
+ def HasField(self, field_name: typing_extensions.Literal["accountIDToUpdate", b"accountIDToUpdate", "autoRenewPeriod", b"autoRenewPeriod", "decline_reward", b"decline_reward", "expirationTime", b"expirationTime", "key", b"key", "max_automatic_token_associations", b"max_automatic_token_associations", "memo", b"memo", "proxyAccountID", b"proxyAccountID", "receiveRecordThreshold", b"receiveRecordThreshold", "receiveRecordThresholdField", b"receiveRecordThresholdField", "receiveRecordThresholdWrapper", b"receiveRecordThresholdWrapper", "receiverSigRequired", b"receiverSigRequired", "receiverSigRequiredField", b"receiverSigRequiredField", "receiverSigRequiredWrapper", b"receiverSigRequiredWrapper", "sendRecordThreshold", b"sendRecordThreshold", "sendRecordThresholdField", b"sendRecordThresholdField", "sendRecordThresholdWrapper", b"sendRecordThresholdWrapper", "staked_account_id", b"staked_account_id", "staked_id", b"staked_id", "staked_node_id", b"staked_node_id"]) -> builtins.bool: ...
+ def ClearField(self, field_name: typing_extensions.Literal["accountIDToUpdate", b"accountIDToUpdate", "autoRenewPeriod", b"autoRenewPeriod", "decline_reward", b"decline_reward", "expirationTime", b"expirationTime", "key", b"key", "max_automatic_token_associations", b"max_automatic_token_associations", "memo", b"memo", "proxyAccountID", b"proxyAccountID", "proxyFraction", b"proxyFraction", "receiveRecordThreshold", b"receiveRecordThreshold", "receiveRecordThresholdField", b"receiveRecordThresholdField", "receiveRecordThresholdWrapper", b"receiveRecordThresholdWrapper", "receiverSigRequired", b"receiverSigRequired", "receiverSigRequiredField", b"receiverSigRequiredField", "receiverSigRequiredWrapper", b"receiverSigRequiredWrapper", "sendRecordThreshold", b"sendRecordThreshold", "sendRecordThresholdField", b"sendRecordThresholdField", "sendRecordThresholdWrapper", b"sendRecordThresholdWrapper", "staked_account_id", b"staked_account_id", "staked_id", b"staked_id", "staked_node_id", b"staked_node_id"]) -> None: ...
+ @typing.overload
+ def WhichOneof(self, oneof_group: typing_extensions.Literal["receiveRecordThresholdField", b"receiveRecordThresholdField"]) -> typing_extensions.Literal["receiveRecordThreshold", "receiveRecordThresholdWrapper"] | None: ...
+ @typing.overload
+ def WhichOneof(self, oneof_group: typing_extensions.Literal["receiverSigRequiredField", b"receiverSigRequiredField"]) -> typing_extensions.Literal["receiverSigRequired", "receiverSigRequiredWrapper"] | None: ...
+ @typing.overload
+ def WhichOneof(self, oneof_group: typing_extensions.Literal["sendRecordThresholdField", b"sendRecordThresholdField"]) -> typing_extensions.Literal["sendRecordThreshold", "sendRecordThresholdWrapper"] | None: ...
+ @typing.overload
+ def WhichOneof(self, oneof_group: typing_extensions.Literal["staked_id", b"staked_id"]) -> typing_extensions.Literal["staked_account_id", "staked_node_id"] | None: ...
+
+global___CryptoUpdateTransactionBody = CryptoUpdateTransactionBody
diff --git a/proto/duration.proto b/proto/duration.proto
new file mode 100644
index 00000000..9d7514c7
--- /dev/null
+++ b/proto/duration.proto
@@ -0,0 +1,34 @@
+
+syntax = "proto3";
+
+package Hedera;
+/*-
+ *
+ * Hedera Network Services Protobuf
+ *
+ * Copyright (C) 2018 - 2021 Hedera Hashgraph, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import "nanopb.proto";
+
+/**
+ * A length of time in seconds.
+ */
+message Duration {
+ /**
+ * The number of seconds
+ */
+ int64 seconds = 1;
+}
\ No newline at end of file
diff --git a/proto/duration_pb2.py b/proto/duration_pb2.py
new file mode 100644
index 00000000..7dae9331
--- /dev/null
+++ b/proto/duration_pb2.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: proto/duration.proto
+"""Generated protocol buffer code."""
+from google.protobuf.internal import builder as _builder
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+import nanopb_pb2 as nanopb__pb2
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14proto/duration.proto\x12\x06Hedera\x1a\x0cnanopb.proto\"\x1b\n\x08\x44uration\x12\x0f\n\x07seconds\x18\x01 \x01(\x03\x62\x06proto3')
+
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'proto.duration_pb2', globals())
+if _descriptor._USE_C_DESCRIPTORS == False:
+
+ DESCRIPTOR._options = None
+ _DURATION._serialized_start=46
+ _DURATION._serialized_end=73
+# @@protoc_insertion_point(module_scope)
diff --git a/proto/duration_pb2.pyi b/proto/duration_pb2.pyi
new file mode 100644
index 00000000..656be12d
--- /dev/null
+++ b/proto/duration_pb2.pyi
@@ -0,0 +1,37 @@
+"""
+@generated by mypy-protobuf. Do not edit manually!
+isort:skip_file
+"""
+import builtins
+import google.protobuf.descriptor
+import google.protobuf.message
+import sys
+
+if sys.version_info >= (3, 8):
+ import typing as typing_extensions
+else:
+ import typing_extensions
+
+DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
+
+@typing_extensions.final
+class Duration(google.protobuf.message.Message):
+ """*
+ A length of time in seconds.
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ SECONDS_FIELD_NUMBER: builtins.int
+ seconds: builtins.int
+ """*
+ The number of seconds
+ """
+ def __init__(
+ self,
+ *,
+ seconds: builtins.int = ...,
+ ) -> None: ...
+ def ClearField(self, field_name: typing_extensions.Literal["seconds", b"seconds"]) -> None: ...
+
+global___Duration = Duration
diff --git a/proto/timestamp.proto b/proto/timestamp.proto
new file mode 100644
index 00000000..4c898e45
--- /dev/null
+++ b/proto/timestamp.proto
@@ -0,0 +1,52 @@
+syntax = "proto3";
+
+package Hedera;
+
+/*-
+ *
+ * Hedera Network Services Protobuf
+ *
+ * Copyright (C) 2018 - 2022 Hedera Hashgraph, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import "nanopb.proto";
+
+/**
+ * An exact date and time. This is the same data structure as the protobuf
+ * Timestamp.proto (see the comments in
+ * https://github.com/google/protobuf
+ * /blob/master/src/google/protobuf/timestamp.proto)
+ */
+message Timestamp {
+ /**
+ * Number of complete seconds since the start of the epoch
+ */
+ int64 seconds = 1;
+
+ /**
+ * Number of nanoseconds since the start of the last second
+ */
+ int32 nanos = 2;
+}
+
+/**
+ * An exact date and time, with a resolution of one second (no nanoseconds).
+ */
+message TimestampSeconds {
+ /**
+ * Number of complete seconds since the start of the epoch
+ */
+ int64 seconds = 1;
+}
diff --git a/proto/timestamp_pb2.py b/proto/timestamp_pb2.py
new file mode 100644
index 00000000..ee8d3d1e
--- /dev/null
+++ b/proto/timestamp_pb2.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: proto/timestamp.proto
+"""Generated protocol buffer code."""
+from google.protobuf.internal import builder as _builder
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+import nanopb_pb2 as nanopb__pb2
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15proto/timestamp.proto\x12\x06Hedera\x1a\x0cnanopb.proto\"+\n\tTimestamp\x12\x0f\n\x07seconds\x18\x01 \x01(\x03\x12\r\n\x05nanos\x18\x02 \x01(\x05\"#\n\x10TimestampSeconds\x12\x0f\n\x07seconds\x18\x01 \x01(\x03\x62\x06proto3')
+
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'proto.timestamp_pb2', globals())
+if _descriptor._USE_C_DESCRIPTORS == False:
+
+ DESCRIPTOR._options = None
+ _TIMESTAMP._serialized_start=47
+ _TIMESTAMP._serialized_end=90
+ _TIMESTAMPSECONDS._serialized_start=92
+ _TIMESTAMPSECONDS._serialized_end=127
+# @@protoc_insertion_point(module_scope)
diff --git a/proto/timestamp_pb2.pyi b/proto/timestamp_pb2.pyi
new file mode 100644
index 00000000..883e7eb1
--- /dev/null
+++ b/proto/timestamp_pb2.pyi
@@ -0,0 +1,68 @@
+"""
+@generated by mypy-protobuf. Do not edit manually!
+isort:skip_file
+"""
+import builtins
+import google.protobuf.descriptor
+import google.protobuf.message
+import sys
+
+if sys.version_info >= (3, 8):
+ import typing as typing_extensions
+else:
+ import typing_extensions
+
+DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
+
+@typing_extensions.final
+class Timestamp(google.protobuf.message.Message):
+ """*
+ An exact date and time. This is the same data structure as the protobuf
+ Timestamp.proto (see the comments in
+ https://github.com/google/protobuf
+ /blob/master/src/google/protobuf/timestamp.proto)
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ SECONDS_FIELD_NUMBER: builtins.int
+ NANOS_FIELD_NUMBER: builtins.int
+ seconds: builtins.int
+ """*
+ Number of complete seconds since the start of the epoch
+ """
+ nanos: builtins.int
+ """*
+ Number of nanoseconds since the start of the last second
+ """
+ def __init__(
+ self,
+ *,
+ seconds: builtins.int = ...,
+ nanos: builtins.int = ...,
+ ) -> None: ...
+ def ClearField(self, field_name: typing_extensions.Literal["nanos", b"nanos", "seconds", b"seconds"]) -> None: ...
+
+global___Timestamp = Timestamp
+
+@typing_extensions.final
+class TimestampSeconds(google.protobuf.message.Message):
+ """*
+ An exact date and time, with a resolution of one second (no nanoseconds).
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ SECONDS_FIELD_NUMBER: builtins.int
+ seconds: builtins.int
+ """*
+ Number of complete seconds since the start of the epoch
+ """
+ def __init__(
+ self,
+ *,
+ seconds: builtins.int = ...,
+ ) -> None: ...
+ def ClearField(self, field_name: typing_extensions.Literal["seconds", b"seconds"]) -> None: ...
+
+global___TimestampSeconds = TimestampSeconds
diff --git a/proto/token_associate.proto b/proto/token_associate.proto
new file mode 100644
index 00000000..2a28fce5
--- /dev/null
+++ b/proto/token_associate.proto
@@ -0,0 +1,55 @@
+syntax = "proto3";
+
+package Hedera;
+
+/*-
+ *
+ * Hedera Network Services Protobuf
+ *
+ * Copyright (C) 2018 - 2021 Hedera Hashgraph, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import "nanopb.proto";
+import "proto/basic_types.proto";
+
+/**
+ * Associates the provided account with the provided tokens. Must be signed by
+ * the provided Account's key. If the provided account is not found, the
+ * transaction will resolve to INVALID_ACCOUNT_ID. If the provided account has
+ * been deleted, the transaction will resolve to ACCOUNT_DELETED. If any of the
+ * provided tokens is not found, the transaction will resolve to
+ * INVALID_TOKEN_REF. If any of the provided tokens has been deleted, the
+ * transaction will resolve to TOKEN_WAS_DELETED. If an association between the
+ * provided account and any of the tokens already exists, the transaction will
+ * resolve to TOKEN_ALREADY_ASSOCIATED_TO_ACCOUNT. If the provided account's
+ * associations count exceed the constraint of maximum token associations per
+ * account, the transaction will resolve to TOKENS_PER_ACCOUNT_LIMIT_EXCEEDED.
+ * On success, associations between the provided account and tokens are made and
+ * the account is ready to interact with the tokens.
+ */
+message TokenAssociateTransactionBody {
+ /**
+ * The account to be associated with the provided tokens
+ */
+ AccountID account = 1;
+
+ /**
+ * The tokens to be associated with the provided account. In the case of
+ * NON_FUNGIBLE_UNIQUE Type, once an account is associated, it can hold any
+ * number of NFTs (serial numbers) of that token type
+ * Limited to 1 here (no access to malloc for dynamic decode!)
+ */
+ repeated TokenID tokens = 2 [ (nanopb).max_count = 1 ];
+}
\ No newline at end of file
diff --git a/proto/token_associate_pb2.py b/proto/token_associate_pb2.py
new file mode 100644
index 00000000..21ee64af
--- /dev/null
+++ b/proto/token_associate_pb2.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: proto/token_associate.proto
+"""Generated protocol buffer code."""
+from google.protobuf.internal import builder as _builder
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+import nanopb_pb2 as nanopb__pb2
+from proto import basic_types_pb2 as proto_dot_basic__types__pb2
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1bproto/token_associate.proto\x12\x06Hedera\x1a\x0cnanopb.proto\x1a\x17proto/basic_types.proto\"k\n\x1dTokenAssociateTransactionBody\x12\"\n\x07\x61\x63\x63ount\x18\x01 \x01(\x0b\x32\x11.Hedera.AccountID\x12&\n\x06tokens\x18\x02 \x03(\x0b\x32\x0f.Hedera.TokenIDB\x05\x92?\x02\x10\x01\x62\x06proto3')
+
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'proto.token_associate_pb2', globals())
+if _descriptor._USE_C_DESCRIPTORS == False:
+
+ DESCRIPTOR._options = None
+ _TOKENASSOCIATETRANSACTIONBODY.fields_by_name['tokens']._options = None
+ _TOKENASSOCIATETRANSACTIONBODY.fields_by_name['tokens']._serialized_options = b'\222?\002\020\001'
+ _TOKENASSOCIATETRANSACTIONBODY._serialized_start=78
+ _TOKENASSOCIATETRANSACTIONBODY._serialized_end=185
+# @@protoc_insertion_point(module_scope)
diff --git a/proto/token_associate_pb2.pyi b/proto/token_associate_pb2.pyi
new file mode 100644
index 00000000..bd01a227
--- /dev/null
+++ b/proto/token_associate_pb2.pyi
@@ -0,0 +1,64 @@
+"""
+@generated by mypy-protobuf. Do not edit manually!
+isort:skip_file
+"""
+import builtins
+import collections.abc
+import google.protobuf.descriptor
+import google.protobuf.internal.containers
+import google.protobuf.message
+import proto.basic_types_pb2
+import sys
+
+if sys.version_info >= (3, 8):
+ import typing as typing_extensions
+else:
+ import typing_extensions
+
+DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
+
+@typing_extensions.final
+class TokenAssociateTransactionBody(google.protobuf.message.Message):
+ """*
+ Associates the provided account with the provided tokens. Must be signed by
+ the provided Account's key. If the provided account is not found, the
+ transaction will resolve to INVALID_ACCOUNT_ID. If the provided account has
+ been deleted, the transaction will resolve to ACCOUNT_DELETED. If any of the
+ provided tokens is not found, the transaction will resolve to
+ INVALID_TOKEN_REF. If any of the provided tokens has been deleted, the
+ transaction will resolve to TOKEN_WAS_DELETED. If an association between the
+ provided account and any of the tokens already exists, the transaction will
+ resolve to TOKEN_ALREADY_ASSOCIATED_TO_ACCOUNT. If the provided account's
+ associations count exceed the constraint of maximum token associations per
+ account, the transaction will resolve to TOKENS_PER_ACCOUNT_LIMIT_EXCEEDED.
+ On success, associations between the provided account and tokens are made and
+ the account is ready to interact with the tokens.
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ ACCOUNT_FIELD_NUMBER: builtins.int
+ TOKENS_FIELD_NUMBER: builtins.int
+ @property
+ def account(self) -> proto.basic_types_pb2.AccountID:
+ """*
+ The account to be associated with the provided tokens
+ """
+ @property
+ def tokens(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[proto.basic_types_pb2.TokenID]:
+ """*
+ The tokens to be associated with the provided account. In the case of
+ NON_FUNGIBLE_UNIQUE Type, once an account is associated, it can hold any
+ number of NFTs (serial numbers) of that token type
+ Limited to 1 here (no access to malloc for dynamic decode!)
+ """
+ def __init__(
+ self,
+ *,
+ account: proto.basic_types_pb2.AccountID | None = ...,
+ tokens: collections.abc.Iterable[proto.basic_types_pb2.TokenID] | None = ...,
+ ) -> None: ...
+ def HasField(self, field_name: typing_extensions.Literal["account", b"account"]) -> builtins.bool: ...
+ def ClearField(self, field_name: typing_extensions.Literal["account", b"account", "tokens", b"tokens"]) -> None: ...
+
+global___TokenAssociateTransactionBody = TokenAssociateTransactionBody
diff --git a/proto/token_burn.proto b/proto/token_burn.proto
new file mode 100644
index 00000000..d89dda89
--- /dev/null
+++ b/proto/token_burn.proto
@@ -0,0 +1,67 @@
+syntax = "proto3";
+
+package Hedera;
+
+/*-
+ *
+ * Hedera Network Services Protobuf
+ *
+ * Copyright (C) 2018 - 2021 Hedera Hashgraph, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import "nanopb.proto";
+import "proto/basic_types.proto";
+
+/**
+ * Burns tokens from the Token's treasury Account. If no Supply Key is defined,
+ * the transaction will resolve to TOKEN_HAS_NO_SUPPLY_KEY. The operation
+ * decreases the Total Supply of the Token. Total supply cannot go below zero.
+ * The amount provided must be in the lowest denomination possible. Example:
+ * Token A has 2 decimals. In order to burn 100 tokens, one must provide amount
+ * of 10000. In order to burn 100.55 tokens, one must provide amount of 10055.
+ * For non fungible tokens the transaction body accepts serialNumbers list of
+ * integers as a parameter.
+ *
+ * If neither the amount nor the serialNumbers get filled, a
+ * INVALID_TOKEN_BURN_AMOUNT response code will be returned. If both amount and
+ * serialNumbers get filled, a INVALID_TRANSACTION_BODY response code will be
+ * returned.
+ * If the serialNumbers' list count is greater than the batch size limit global
+ * dynamic property, a BATCH_SIZE_LIMIT_EXCEEDED response code will be returned.
+ * If the serialNumbers list contains a non-positive integer as a serial number,
+ * a INVALID_NFT_ID response code will be returned.
+ */
+message TokenBurnTransactionBody {
+ /**
+ * The token for which to burn tokens. If token does not exist, transaction
+ * results in INVALID_TOKEN_ID
+ */
+ TokenID token = 1;
+
+ /**
+ * Applicable to tokens of type FUNGIBLE_COMMON. The amount to burn from the
+ * Treasury Account. Amount must be a positive non-zero number, not bigger
+ * than the token balance of the treasury account (0; balance], represented in
+ * the lowest denomination.
+ */
+ uint64 amount = 2;
+
+ /**
+ * Applicable to tokens of type NON_FUNGIBLE_UNIQUE. The list of serial
+ * numbers to be burned.
+ * Limited to 1 here
+ */
+ repeated int64 serialNumbers = 3 [ (nanopb).max_count = 1 ];
+}
\ No newline at end of file
diff --git a/proto/token_burn_pb2.py b/proto/token_burn_pb2.py
new file mode 100644
index 00000000..05efdf3f
--- /dev/null
+++ b/proto/token_burn_pb2.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: proto/token_burn.proto
+"""Generated protocol buffer code."""
+from google.protobuf.internal import builder as _builder
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+import nanopb_pb2 as nanopb__pb2
+from proto import basic_types_pb2 as proto_dot_basic__types__pb2
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16proto/token_burn.proto\x12\x06Hedera\x1a\x0cnanopb.proto\x1a\x17proto/basic_types.proto\"h\n\x18TokenBurnTransactionBody\x12\x1e\n\x05token\x18\x01 \x01(\x0b\x32\x0f.Hedera.TokenID\x12\x0e\n\x06\x61mount\x18\x02 \x01(\x04\x12\x1c\n\rserialNumbers\x18\x03 \x03(\x03\x42\x05\x92?\x02\x10\x01\x62\x06proto3')
+
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'proto.token_burn_pb2', globals())
+if _descriptor._USE_C_DESCRIPTORS == False:
+
+ DESCRIPTOR._options = None
+ _TOKENBURNTRANSACTIONBODY.fields_by_name['serialNumbers']._options = None
+ _TOKENBURNTRANSACTIONBODY.fields_by_name['serialNumbers']._serialized_options = b'\222?\002\020\001'
+ _TOKENBURNTRANSACTIONBODY._serialized_start=73
+ _TOKENBURNTRANSACTIONBODY._serialized_end=177
+# @@protoc_insertion_point(module_scope)
diff --git a/proto/token_burn_pb2.pyi b/proto/token_burn_pb2.pyi
new file mode 100644
index 00000000..dc77477a
--- /dev/null
+++ b/proto/token_burn_pb2.pyi
@@ -0,0 +1,77 @@
+"""
+@generated by mypy-protobuf. Do not edit manually!
+isort:skip_file
+"""
+import builtins
+import collections.abc
+import google.protobuf.descriptor
+import google.protobuf.internal.containers
+import google.protobuf.message
+import proto.basic_types_pb2
+import sys
+
+if sys.version_info >= (3, 8):
+ import typing as typing_extensions
+else:
+ import typing_extensions
+
+DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
+
+@typing_extensions.final
+class TokenBurnTransactionBody(google.protobuf.message.Message):
+ """*
+ Burns tokens from the Token's treasury Account. If no Supply Key is defined,
+ the transaction will resolve to TOKEN_HAS_NO_SUPPLY_KEY. The operation
+ decreases the Total Supply of the Token. Total supply cannot go below zero.
+ The amount provided must be in the lowest denomination possible. Example:
+ Token A has 2 decimals. In order to burn 100 tokens, one must provide amount
+ of 10000. In order to burn 100.55 tokens, one must provide amount of 10055.
+ For non fungible tokens the transaction body accepts serialNumbers list of
+ integers as a parameter.
+
+ If neither the amount nor the serialNumbers get filled, a
+ INVALID_TOKEN_BURN_AMOUNT response code will be returned. If both amount and
+ serialNumbers get filled, a INVALID_TRANSACTION_BODY response code will be
+ returned.
+ If the serialNumbers' list count is greater than the batch size limit global
+ dynamic property, a BATCH_SIZE_LIMIT_EXCEEDED response code will be returned.
+ If the serialNumbers list contains a non-positive integer as a serial number,
+ a INVALID_NFT_ID response code will be returned.
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ TOKEN_FIELD_NUMBER: builtins.int
+ AMOUNT_FIELD_NUMBER: builtins.int
+ SERIALNUMBERS_FIELD_NUMBER: builtins.int
+ @property
+ def token(self) -> proto.basic_types_pb2.TokenID:
+ """*
+ The token for which to burn tokens. If token does not exist, transaction
+ results in INVALID_TOKEN_ID
+ """
+ amount: builtins.int
+ """*
+ Applicable to tokens of type FUNGIBLE_COMMON. The amount to burn from the
+ Treasury Account. Amount must be a positive non-zero number, not bigger
+ than the token balance of the treasury account (0; balance], represented in
+ the lowest denomination.
+ """
+ @property
+ def serialNumbers(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]:
+ """*
+ Applicable to tokens of type NON_FUNGIBLE_UNIQUE. The list of serial
+ numbers to be burned.
+ Limited to 1 here
+ """
+ def __init__(
+ self,
+ *,
+ token: proto.basic_types_pb2.TokenID | None = ...,
+ amount: builtins.int = ...,
+ serialNumbers: collections.abc.Iterable[builtins.int] | None = ...,
+ ) -> None: ...
+ def HasField(self, field_name: typing_extensions.Literal["token", b"token"]) -> builtins.bool: ...
+ def ClearField(self, field_name: typing_extensions.Literal["amount", b"amount", "serialNumbers", b"serialNumbers", "token", b"token"]) -> None: ...
+
+global___TokenBurnTransactionBody = TokenBurnTransactionBody
diff --git a/proto/token_dissociate.proto b/proto/token_dissociate.proto
new file mode 100644
index 00000000..4e75f3c7
--- /dev/null
+++ b/proto/token_dissociate.proto
@@ -0,0 +1,56 @@
+syntax = "proto3";
+
+package Hedera;
+
+/*-
+ *
+ * Hedera Network Services Protobuf
+ *
+ * Copyright (C) 2018 - 2021 Hedera Hashgraph, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import "nanopb.proto";
+import "proto/basic_types.proto";
+
+/**
+ * Dissociates the provided account with the provided tokens. Must be signed by
+ * the provided Account's key. If the provided account is not found, the
+ * transaction will resolve to INVALID_ACCOUNT_ID. If the provided account has
+ * been deleted, the transaction will resolve to ACCOUNT_DELETED. If any of the
+ * provided tokens is not found, the transaction will resolve to
+ * INVALID_TOKEN_REF. If any of the provided tokens has been deleted, the
+ * transaction will resolve to TOKEN_WAS_DELETED. If an association between the
+ * provided account and any of the tokens does not exist, the transaction will
+ * resolve to TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. If a token has not been deleted
+ * and has not expired, and the user has a nonzero balance, the transaction will
+ * resolve to TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES. If a fungible
+ * token has expired, the user can disassociate even if their token balance
+ * is not zero. If a non fungible token has expired, the user can
+ * not disassociate if their token balance is not zero. The transaction
+ * will resolve to TRANSACTION_REQUIRED_ZERO_TOKEN_BALANCES. On success,
+ * associations between the provided account and tokens are removed.
+ */
+message TokenDissociateTransactionBody {
+ /**
+ * The account to be dissociated with the provided tokens
+ */
+ AccountID account = 1;
+
+ /**
+ * The tokens to be dissociated with the provided account
+ * Limited to 1 here
+ */
+ repeated TokenID tokens = 2 [ (nanopb).max_count = 1 ];
+}
\ No newline at end of file
diff --git a/proto/token_dissociate_pb2.py b/proto/token_dissociate_pb2.py
new file mode 100644
index 00000000..b5809df2
--- /dev/null
+++ b/proto/token_dissociate_pb2.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: proto/token_dissociate.proto
+"""Generated protocol buffer code."""
+from google.protobuf.internal import builder as _builder
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+import nanopb_pb2 as nanopb__pb2
+from proto import basic_types_pb2 as proto_dot_basic__types__pb2
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1cproto/token_dissociate.proto\x12\x06Hedera\x1a\x0cnanopb.proto\x1a\x17proto/basic_types.proto\"l\n\x1eTokenDissociateTransactionBody\x12\"\n\x07\x61\x63\x63ount\x18\x01 \x01(\x0b\x32\x11.Hedera.AccountID\x12&\n\x06tokens\x18\x02 \x03(\x0b\x32\x0f.Hedera.TokenIDB\x05\x92?\x02\x10\x01\x62\x06proto3')
+
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'proto.token_dissociate_pb2', globals())
+if _descriptor._USE_C_DESCRIPTORS == False:
+
+ DESCRIPTOR._options = None
+ _TOKENDISSOCIATETRANSACTIONBODY.fields_by_name['tokens']._options = None
+ _TOKENDISSOCIATETRANSACTIONBODY.fields_by_name['tokens']._serialized_options = b'\222?\002\020\001'
+ _TOKENDISSOCIATETRANSACTIONBODY._serialized_start=79
+ _TOKENDISSOCIATETRANSACTIONBODY._serialized_end=187
+# @@protoc_insertion_point(module_scope)
diff --git a/proto/token_dissociate_pb2.pyi b/proto/token_dissociate_pb2.pyi
new file mode 100644
index 00000000..e5a665e6
--- /dev/null
+++ b/proto/token_dissociate_pb2.pyi
@@ -0,0 +1,65 @@
+"""
+@generated by mypy-protobuf. Do not edit manually!
+isort:skip_file
+"""
+import builtins
+import collections.abc
+import google.protobuf.descriptor
+import google.protobuf.internal.containers
+import google.protobuf.message
+import proto.basic_types_pb2
+import sys
+
+if sys.version_info >= (3, 8):
+ import typing as typing_extensions
+else:
+ import typing_extensions
+
+DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
+
+@typing_extensions.final
+class TokenDissociateTransactionBody(google.protobuf.message.Message):
+ """*
+ Dissociates the provided account with the provided tokens. Must be signed by
+ the provided Account's key. If the provided account is not found, the
+ transaction will resolve to INVALID_ACCOUNT_ID. If the provided account has
+ been deleted, the transaction will resolve to ACCOUNT_DELETED. If any of the
+ provided tokens is not found, the transaction will resolve to
+ INVALID_TOKEN_REF. If any of the provided tokens has been deleted, the
+ transaction will resolve to TOKEN_WAS_DELETED. If an association between the
+ provided account and any of the tokens does not exist, the transaction will
+ resolve to TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. If a token has not been deleted
+ and has not expired, and the user has a nonzero balance, the transaction will
+ resolve to TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES. If a fungible
+ token has expired, the user can disassociate even if their token balance
+ is not zero. If a non fungible token has expired, the user can
+ not disassociate if their token balance is not zero. The transaction
+ will resolve to TRANSACTION_REQUIRED_ZERO_TOKEN_BALANCES. On success,
+ associations between the provided account and tokens are removed.
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ ACCOUNT_FIELD_NUMBER: builtins.int
+ TOKENS_FIELD_NUMBER: builtins.int
+ @property
+ def account(self) -> proto.basic_types_pb2.AccountID:
+ """*
+ The account to be dissociated with the provided tokens
+ """
+ @property
+ def tokens(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[proto.basic_types_pb2.TokenID]:
+ """*
+ The tokens to be dissociated with the provided account
+ Limited to 1 here
+ """
+ def __init__(
+ self,
+ *,
+ account: proto.basic_types_pb2.AccountID | None = ...,
+ tokens: collections.abc.Iterable[proto.basic_types_pb2.TokenID] | None = ...,
+ ) -> None: ...
+ def HasField(self, field_name: typing_extensions.Literal["account", b"account"]) -> builtins.bool: ...
+ def ClearField(self, field_name: typing_extensions.Literal["account", b"account", "tokens", b"tokens"]) -> None: ...
+
+global___TokenDissociateTransactionBody = TokenDissociateTransactionBody
diff --git a/proto/token_mint.proto b/proto/token_mint.proto
new file mode 100644
index 00000000..1c81866a
--- /dev/null
+++ b/proto/token_mint.proto
@@ -0,0 +1,65 @@
+syntax = "proto3";
+
+package Hedera;
+
+/*-
+ *
+ * Hedera Network Services Protobuf
+ *
+ * Copyright (C) 2018 - 2021 Hedera Hashgraph, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import "nanopb.proto";
+import "proto/basic_types.proto";
+
+/**
+ * Mints tokens to the Token's treasury Account. If no Supply Key is defined,
+ * the transaction will resolve to TOKEN_HAS_NO_SUPPLY_KEY. The operation
+ * increases the Total Supply of the Token. The maximum total supply a token can
+ * have is 2^63-1. The amount provided must be in the lowest denomination
+ * possible. Example: Token A has 2 decimals. In order to mint 100 tokens, one
+ * must provide amount of 10000. In order to mint 100.55 tokens, one must
+ * provide amount of 10055. If both amount and metadata list get filled, a
+ * INVALID_TRANSACTION_BODY response code will be returned. If the metadata list
+ * contains metadata which is too large, a METADATA_TOO_LONG response code will
+ * be returned.
+ * If neither the amount nor the metadata list get filled, a
+ * INVALID_TOKEN_MINT_AMOUNT response code will be returned. If the metadata
+ * list count is greater than the batch size limit global dynamic property, a
+ * BATCH_SIZE_LIMIT_EXCEEDED response code will be returned.
+ */
+message TokenMintTransactionBody {
+ /**
+ * The token for which to mint tokens. If token does not exist, transaction
+ * results in INVALID_TOKEN_ID
+ */
+ TokenID token = 1;
+
+ /**
+ * Applicable to tokens of type FUNGIBLE_COMMON. The amount to mint to the
+ * Treasury Account. Amount must be a positive non-zero number represented in
+ * the lowest denomination of the token. The new supply must be lower than
+ * 2^63.
+ */
+ uint64 amount = 2;
+
+ /**
+ * Applicable to tokens of type NON_FUNGIBLE_UNIQUE. A list of metadata that
+ * are being created. Maximum allowed size of each metadata is 100 bytes
+ * Limited to 1 metadata chunk (no access to malloc)
+ */
+ repeated bytes metadata = 3
+ [ (nanopb).max_size = 100, (nanopb).max_count = 1 ];
+}
\ No newline at end of file
diff --git a/proto/token_mint_pb2.py b/proto/token_mint_pb2.py
new file mode 100644
index 00000000..0ea6629c
--- /dev/null
+++ b/proto/token_mint_pb2.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: proto/token_mint.proto
+"""Generated protocol buffer code."""
+from google.protobuf.internal import builder as _builder
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+import nanopb_pb2 as nanopb__pb2
+from proto import basic_types_pb2 as proto_dot_basic__types__pb2
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16proto/token_mint.proto\x12\x06Hedera\x1a\x0cnanopb.proto\x1a\x17proto/basic_types.proto\"h\n\x18TokenMintTransactionBody\x12\x1e\n\x05token\x18\x01 \x01(\x0b\x32\x0f.Hedera.TokenID\x12\x0e\n\x06\x61mount\x18\x02 \x01(\x04\x12\x1c\n\x08metadata\x18\x03 \x03(\x0c\x42\n\x92?\x02\x08\x64\x92?\x02\x10\x01\x62\x06proto3')
+
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'proto.token_mint_pb2', globals())
+if _descriptor._USE_C_DESCRIPTORS == False:
+
+ DESCRIPTOR._options = None
+ _TOKENMINTTRANSACTIONBODY.fields_by_name['metadata']._options = None
+ _TOKENMINTTRANSACTIONBODY.fields_by_name['metadata']._serialized_options = b'\222?\002\010d\222?\002\020\001'
+ _TOKENMINTTRANSACTIONBODY._serialized_start=73
+ _TOKENMINTTRANSACTIONBODY._serialized_end=177
+# @@protoc_insertion_point(module_scope)
diff --git a/proto/token_mint_pb2.pyi b/proto/token_mint_pb2.pyi
new file mode 100644
index 00000000..2cece623
--- /dev/null
+++ b/proto/token_mint_pb2.pyi
@@ -0,0 +1,74 @@
+"""
+@generated by mypy-protobuf. Do not edit manually!
+isort:skip_file
+"""
+import builtins
+import collections.abc
+import google.protobuf.descriptor
+import google.protobuf.internal.containers
+import google.protobuf.message
+import proto.basic_types_pb2
+import sys
+
+if sys.version_info >= (3, 8):
+ import typing as typing_extensions
+else:
+ import typing_extensions
+
+DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
+
+@typing_extensions.final
+class TokenMintTransactionBody(google.protobuf.message.Message):
+ """*
+ Mints tokens to the Token's treasury Account. If no Supply Key is defined,
+ the transaction will resolve to TOKEN_HAS_NO_SUPPLY_KEY. The operation
+ increases the Total Supply of the Token. The maximum total supply a token can
+ have is 2^63-1. The amount provided must be in the lowest denomination
+ possible. Example: Token A has 2 decimals. In order to mint 100 tokens, one
+ must provide amount of 10000. In order to mint 100.55 tokens, one must
+ provide amount of 10055. If both amount and metadata list get filled, a
+ INVALID_TRANSACTION_BODY response code will be returned. If the metadata list
+ contains metadata which is too large, a METADATA_TOO_LONG response code will
+ be returned.
+ If neither the amount nor the metadata list get filled, a
+ INVALID_TOKEN_MINT_AMOUNT response code will be returned. If the metadata
+ list count is greater than the batch size limit global dynamic property, a
+ BATCH_SIZE_LIMIT_EXCEEDED response code will be returned.
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ TOKEN_FIELD_NUMBER: builtins.int
+ AMOUNT_FIELD_NUMBER: builtins.int
+ METADATA_FIELD_NUMBER: builtins.int
+ @property
+ def token(self) -> proto.basic_types_pb2.TokenID:
+ """*
+ The token for which to mint tokens. If token does not exist, transaction
+ results in INVALID_TOKEN_ID
+ """
+ amount: builtins.int
+ """*
+ Applicable to tokens of type FUNGIBLE_COMMON. The amount to mint to the
+ Treasury Account. Amount must be a positive non-zero number represented in
+ the lowest denomination of the token. The new supply must be lower than
+ 2^63.
+ """
+ @property
+ def metadata(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.bytes]:
+ """*
+ Applicable to tokens of type NON_FUNGIBLE_UNIQUE. A list of metadata that
+ are being created. Maximum allowed size of each metadata is 100 bytes
+ Limited to 1 metadata chunk (no access to malloc)
+ """
+ def __init__(
+ self,
+ *,
+ token: proto.basic_types_pb2.TokenID | None = ...,
+ amount: builtins.int = ...,
+ metadata: collections.abc.Iterable[builtins.bytes] | None = ...,
+ ) -> None: ...
+ def HasField(self, field_name: typing_extensions.Literal["token", b"token"]) -> builtins.bool: ...
+ def ClearField(self, field_name: typing_extensions.Literal["amount", b"amount", "metadata", b"metadata", "token", b"token"]) -> None: ...
+
+global___TokenMintTransactionBody = TokenMintTransactionBody
diff --git a/proto/transaction_body.proto b/proto/transaction_body.proto
new file mode 100644
index 00000000..73a5ca6b
--- /dev/null
+++ b/proto/transaction_body.proto
@@ -0,0 +1,117 @@
+syntax = "proto3";
+
+package Hedera;
+
+/*-
+ *
+ * Hedera Network Services Protobuf
+ *
+ * Copyright (C) 2018 - 2021 Hedera Hashgraph, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import "nanopb.proto";
+import "proto/basic_types.proto";
+import "proto/crypto_create.proto";
+import "proto/crypto_transfer.proto";
+import "proto/crypto_update.proto";
+import "proto/duration.proto";
+import "proto/token_associate.proto";
+import "proto/token_burn.proto";
+import "proto/token_dissociate.proto";
+import "proto/token_mint.proto";
+
+/**
+ * A single transaction. All transaction types are possible here.
+ */
+message TransactionBody {
+ /**
+ * The ID for this transaction, which includes the payer's account (the
+ * account paying the transaction fee). If two transactions have the same
+ * transactionID, they won't both have an effect
+ */
+ TransactionID transactionID = 1;
+
+ /**
+ * The account of the node that submits the client's transaction to the
+ * network
+ */
+ AccountID nodeAccountID = 2;
+
+ /**
+ * The maximum transaction fee the client is willing to pay
+ */
+ uint64 transactionFee = 3;
+
+ /**
+ * The transaction is invalid if consensusTimestamp >
+ * transactionID.transactionValidStart + transactionValidDuration
+ */
+ Duration transactionValidDuration = 4;
+
+ /**
+ * Should a record of this transaction be generated? (A receipt is always
+ * generated, but the record is optional)
+ */
+ bool generateRecord = 5 [ deprecated = true ];
+
+ /**
+ * Any notes or descriptions that should be put into the record (max length
+ * 100)
+ */
+ string memo = 6 [ (nanopb).max_size = 100 ];
+
+ /**
+ * The choices here are arranged by service in roughly lexicographical order.
+ * The field ordinals are non-sequential, and a result of the historical order
+ * of implementation.
+ * Limited to supported transactions here
+ */
+ oneof data {
+ /**
+ * Create a new cryptocurrency account
+ */
+ CryptoCreateTransactionBody cryptoCreateAccount = 11;
+
+ /**
+ * Transfer amount between accounts
+ */
+ CryptoTransferTransactionBody cryptoTransfer = 14;
+
+ /**
+ * Modify information such as the expiration date for an account
+ */
+ CryptoUpdateTransactionBody cryptoUpdateAccount = 15;
+
+ /**
+ * Mints new tokens to a token's treasury account
+ */
+ TokenMintTransactionBody tokenMint = 37;
+
+ /**
+ * Burns tokens from a token's treasury account
+ */
+ TokenBurnTransactionBody tokenBurn = 38;
+
+ /**
+ * Associate tokens to an account
+ */
+ TokenAssociateTransactionBody tokenAssociate = 40;
+
+ /**
+ * Dissociate tokens from an account
+ */
+ TokenDissociateTransactionBody tokenDissociate = 41;
+ }
+}
\ No newline at end of file
diff --git a/proto/transaction_body_pb2.py b/proto/transaction_body_pb2.py
new file mode 100644
index 00000000..e24c5440
--- /dev/null
+++ b/proto/transaction_body_pb2.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: proto/transaction_body.proto
+"""Generated protocol buffer code."""
+from google.protobuf.internal import builder as _builder
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+import nanopb_pb2 as nanopb__pb2
+from proto import basic_types_pb2 as proto_dot_basic__types__pb2
+from proto import crypto_create_pb2 as proto_dot_crypto__create__pb2
+from proto import crypto_transfer_pb2 as proto_dot_crypto__transfer__pb2
+from proto import crypto_update_pb2 as proto_dot_crypto__update__pb2
+from proto import duration_pb2 as proto_dot_duration__pb2
+from proto import token_associate_pb2 as proto_dot_token__associate__pb2
+from proto import token_burn_pb2 as proto_dot_token__burn__pb2
+from proto import token_dissociate_pb2 as proto_dot_token__dissociate__pb2
+from proto import token_mint_pb2 as proto_dot_token__mint__pb2
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1cproto/transaction_body.proto\x12\x06Hedera\x1a\x0cnanopb.proto\x1a\x17proto/basic_types.proto\x1a\x19proto/crypto_create.proto\x1a\x1bproto/crypto_transfer.proto\x1a\x19proto/crypto_update.proto\x1a\x14proto/duration.proto\x1a\x1bproto/token_associate.proto\x1a\x16proto/token_burn.proto\x1a\x1cproto/token_dissociate.proto\x1a\x16proto/token_mint.proto\"\xa9\x05\n\x0fTransactionBody\x12,\n\rtransactionID\x18\x01 \x01(\x0b\x32\x15.Hedera.TransactionID\x12(\n\rnodeAccountID\x18\x02 \x01(\x0b\x32\x11.Hedera.AccountID\x12\x16\n\x0etransactionFee\x18\x03 \x01(\x04\x12\x32\n\x18transactionValidDuration\x18\x04 \x01(\x0b\x32\x10.Hedera.Duration\x12\x1a\n\x0egenerateRecord\x18\x05 \x01(\x08\x42\x02\x18\x01\x12\x13\n\x04memo\x18\x06 \x01(\tB\x05\x92?\x02\x08\x64\x12\x42\n\x13\x63ryptoCreateAccount\x18\x0b \x01(\x0b\x32#.Hedera.CryptoCreateTransactionBodyH\x00\x12?\n\x0e\x63ryptoTransfer\x18\x0e \x01(\x0b\x32%.Hedera.CryptoTransferTransactionBodyH\x00\x12\x42\n\x13\x63ryptoUpdateAccount\x18\x0f \x01(\x0b\x32#.Hedera.CryptoUpdateTransactionBodyH\x00\x12\x35\n\ttokenMint\x18% \x01(\x0b\x32 .Hedera.TokenMintTransactionBodyH\x00\x12\x35\n\ttokenBurn\x18& \x01(\x0b\x32 .Hedera.TokenBurnTransactionBodyH\x00\x12?\n\x0etokenAssociate\x18( \x01(\x0b\x32%.Hedera.TokenAssociateTransactionBodyH\x00\x12\x41\n\x0ftokenDissociate\x18) \x01(\x0b\x32&.Hedera.TokenDissociateTransactionBodyH\x00\x42\x06\n\x04\x64\x61tab\x06proto3')
+
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'proto.transaction_body_pb2', globals())
+if _descriptor._USE_C_DESCRIPTORS == False:
+
+ DESCRIPTOR._options = None
+ _TRANSACTIONBODY.fields_by_name['generateRecord']._options = None
+ _TRANSACTIONBODY.fields_by_name['generateRecord']._serialized_options = b'\030\001'
+ _TRANSACTIONBODY.fields_by_name['memo']._options = None
+ _TRANSACTIONBODY.fields_by_name['memo']._serialized_options = b'\222?\002\010d'
+ _TRANSACTIONBODY._serialized_start=292
+ _TRANSACTIONBODY._serialized_end=973
+# @@protoc_insertion_point(module_scope)
diff --git a/proto/transaction_body_pb2.pyi b/proto/transaction_body_pb2.pyi
new file mode 100644
index 00000000..e2374bdb
--- /dev/null
+++ b/proto/transaction_body_pb2.pyi
@@ -0,0 +1,136 @@
+"""
+@generated by mypy-protobuf. Do not edit manually!
+isort:skip_file
+"""
+import builtins
+import google.protobuf.descriptor
+import google.protobuf.message
+import proto.basic_types_pb2
+import proto.crypto_create_pb2
+import proto.crypto_transfer_pb2
+import proto.crypto_update_pb2
+import proto.duration_pb2
+import proto.token_associate_pb2
+import proto.token_burn_pb2
+import proto.token_dissociate_pb2
+import proto.token_mint_pb2
+import sys
+
+if sys.version_info >= (3, 8):
+ import typing as typing_extensions
+else:
+ import typing_extensions
+
+DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
+
+@typing_extensions.final
+class TransactionBody(google.protobuf.message.Message):
+ """*
+ A single transaction. All transaction types are possible here.
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ TRANSACTIONID_FIELD_NUMBER: builtins.int
+ NODEACCOUNTID_FIELD_NUMBER: builtins.int
+ TRANSACTIONFEE_FIELD_NUMBER: builtins.int
+ TRANSACTIONVALIDDURATION_FIELD_NUMBER: builtins.int
+ GENERATERECORD_FIELD_NUMBER: builtins.int
+ MEMO_FIELD_NUMBER: builtins.int
+ CRYPTOCREATEACCOUNT_FIELD_NUMBER: builtins.int
+ CRYPTOTRANSFER_FIELD_NUMBER: builtins.int
+ CRYPTOUPDATEACCOUNT_FIELD_NUMBER: builtins.int
+ TOKENMINT_FIELD_NUMBER: builtins.int
+ TOKENBURN_FIELD_NUMBER: builtins.int
+ TOKENASSOCIATE_FIELD_NUMBER: builtins.int
+ TOKENDISSOCIATE_FIELD_NUMBER: builtins.int
+ @property
+ def transactionID(self) -> proto.basic_types_pb2.TransactionID:
+ """*
+ The ID for this transaction, which includes the payer's account (the
+ account paying the transaction fee). If two transactions have the same
+ transactionID, they won't both have an effect
+ """
+ @property
+ def nodeAccountID(self) -> proto.basic_types_pb2.AccountID:
+ """*
+ The account of the node that submits the client's transaction to the
+ network
+ """
+ transactionFee: builtins.int
+ """*
+ The maximum transaction fee the client is willing to pay
+ """
+ @property
+ def transactionValidDuration(self) -> proto.duration_pb2.Duration:
+ """*
+ The transaction is invalid if consensusTimestamp >
+ transactionID.transactionValidStart + transactionValidDuration
+ """
+ generateRecord: builtins.bool
+ """*
+ Should a record of this transaction be generated? (A receipt is always
+ generated, but the record is optional)
+ """
+ memo: builtins.str
+ """*
+ Any notes or descriptions that should be put into the record (max length
+ 100)
+ """
+ @property
+ def cryptoCreateAccount(self) -> proto.crypto_create_pb2.CryptoCreateTransactionBody:
+ """*
+ Create a new cryptocurrency account
+ """
+ @property
+ def cryptoTransfer(self) -> proto.crypto_transfer_pb2.CryptoTransferTransactionBody:
+ """*
+ Transfer amount between accounts
+ """
+ @property
+ def cryptoUpdateAccount(self) -> proto.crypto_update_pb2.CryptoUpdateTransactionBody:
+ """*
+ Modify information such as the expiration date for an account
+ """
+ @property
+ def tokenMint(self) -> proto.token_mint_pb2.TokenMintTransactionBody:
+ """*
+ Mints new tokens to a token's treasury account
+ """
+ @property
+ def tokenBurn(self) -> proto.token_burn_pb2.TokenBurnTransactionBody:
+ """*
+ Burns tokens from a token's treasury account
+ """
+ @property
+ def tokenAssociate(self) -> proto.token_associate_pb2.TokenAssociateTransactionBody:
+ """*
+ Associate tokens to an account
+ """
+ @property
+ def tokenDissociate(self) -> proto.token_dissociate_pb2.TokenDissociateTransactionBody:
+ """*
+ Dissociate tokens from an account
+ """
+ def __init__(
+ self,
+ *,
+ transactionID: proto.basic_types_pb2.TransactionID | None = ...,
+ nodeAccountID: proto.basic_types_pb2.AccountID | None = ...,
+ transactionFee: builtins.int = ...,
+ transactionValidDuration: proto.duration_pb2.Duration | None = ...,
+ generateRecord: builtins.bool = ...,
+ memo: builtins.str = ...,
+ cryptoCreateAccount: proto.crypto_create_pb2.CryptoCreateTransactionBody | None = ...,
+ cryptoTransfer: proto.crypto_transfer_pb2.CryptoTransferTransactionBody | None = ...,
+ cryptoUpdateAccount: proto.crypto_update_pb2.CryptoUpdateTransactionBody | None = ...,
+ tokenMint: proto.token_mint_pb2.TokenMintTransactionBody | None = ...,
+ tokenBurn: proto.token_burn_pb2.TokenBurnTransactionBody | None = ...,
+ tokenAssociate: proto.token_associate_pb2.TokenAssociateTransactionBody | None = ...,
+ tokenDissociate: proto.token_dissociate_pb2.TokenDissociateTransactionBody | None = ...,
+ ) -> None: ...
+ def HasField(self, field_name: typing_extensions.Literal["cryptoCreateAccount", b"cryptoCreateAccount", "cryptoTransfer", b"cryptoTransfer", "cryptoUpdateAccount", b"cryptoUpdateAccount", "data", b"data", "nodeAccountID", b"nodeAccountID", "tokenAssociate", b"tokenAssociate", "tokenBurn", b"tokenBurn", "tokenDissociate", b"tokenDissociate", "tokenMint", b"tokenMint", "transactionID", b"transactionID", "transactionValidDuration", b"transactionValidDuration"]) -> builtins.bool: ...
+ def ClearField(self, field_name: typing_extensions.Literal["cryptoCreateAccount", b"cryptoCreateAccount", "cryptoTransfer", b"cryptoTransfer", "cryptoUpdateAccount", b"cryptoUpdateAccount", "data", b"data", "generateRecord", b"generateRecord", "memo", b"memo", "nodeAccountID", b"nodeAccountID", "tokenAssociate", b"tokenAssociate", "tokenBurn", b"tokenBurn", "tokenDissociate", b"tokenDissociate", "tokenMint", b"tokenMint", "transactionFee", b"transactionFee", "transactionID", b"transactionID", "transactionValidDuration", b"transactionValidDuration"]) -> None: ...
+ def WhichOneof(self, oneof_group: typing_extensions.Literal["data", b"data"]) -> typing_extensions.Literal["cryptoCreateAccount", "cryptoTransfer", "cryptoUpdateAccount", "tokenMint", "tokenBurn", "tokenAssociate", "tokenDissociate"] | None: ...
+
+global___TransactionBody = TransactionBody
diff --git a/proto/wrappers.proto b/proto/wrappers.proto
new file mode 100644
index 00000000..d7f95475
--- /dev/null
+++ b/proto/wrappers.proto
@@ -0,0 +1,108 @@
+
+syntax = "proto3";
+
+package Hedera;
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Wrappers for primitive (non-message) types. These types are useful
+// for embedding primitives in the `google.protobuf.Any` type and for places
+// where we need to distinguish between the absence of a primitive
+// typed field and its default value.
+//
+// These wrappers have no meaningful use within repeated fields as they lack
+// the ability to detect presence on individual elements.
+// These wrappers have no meaningful use within a map or a oneof since
+// individual entries of a map or fields of a oneof can already detect presence.
+import "nanopb.proto";
+
+// Wrapper message for `double`.
+//
+// The JSON representation for `DoubleValue` is JSON number.
+message DoubleValue {
+ // The double value.
+ double value = 1;
+}
+
+// Wrapper message for `float`.
+//
+// The JSON representation for `FloatValue` is JSON number.
+message FloatValue {
+ // The float value.
+ float value = 1;
+}
+
+// Wrapper message for `int64`.
+//
+// The JSON representation for `Int64Value` is JSON string.
+message Int64Value {
+ // The int64 value.
+ int64 value = 1;
+}
+
+// Wrapper message for `uint64`.
+//
+// The JSON representation for `UInt64Value` is JSON string.
+message UInt64Value {
+ // The uint64 value.
+ uint64 value = 1;
+}
+
+// Wrapper message for `int32`.
+//
+// The JSON representation for `Int32Value` is JSON number.
+message Int32Value {
+ // The int32 value.
+ int32 value = 1;
+}
+
+// Wrapper message for `uint32`.
+//
+// The JSON representation for `UInt32Value` is JSON number.
+message UInt32Value {
+ // The uint32 value.
+ uint32 value = 1;
+}
+
+// Wrapper message for `bool`.
+//
+// The JSON representation for `BoolValue` is JSON `true` and `false`.
+message BoolValue {
+ // The bool value.
+ bool value = 1;
+}
+
+// Wrapper message for `string`.
+//
+// The JSON representation for `StringValue` is JSON string.
+message StringValue {
+ // The string value.
+ string value = 1;
+}
\ No newline at end of file
diff --git a/proto/wrappers_pb2.py b/proto/wrappers_pb2.py
new file mode 100644
index 00000000..7961d1ed
--- /dev/null
+++ b/proto/wrappers_pb2.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: proto/wrappers.proto
+"""Generated protocol buffer code."""
+from google.protobuf.internal import builder as _builder
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+import nanopb_pb2 as nanopb__pb2
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14proto/wrappers.proto\x12\x06Hedera\x1a\x0cnanopb.proto\"\x1c\n\x0b\x44oubleValue\x12\r\n\x05value\x18\x01 \x01(\x01\"\x1b\n\nFloatValue\x12\r\n\x05value\x18\x01 \x01(\x02\"\x1b\n\nInt64Value\x12\r\n\x05value\x18\x01 \x01(\x03\"\x1c\n\x0bUInt64Value\x12\r\n\x05value\x18\x01 \x01(\x04\"\x1b\n\nInt32Value\x12\r\n\x05value\x18\x01 \x01(\x05\"\x1c\n\x0bUInt32Value\x12\r\n\x05value\x18\x01 \x01(\r\"\x1a\n\tBoolValue\x12\r\n\x05value\x18\x01 \x01(\x08\"\x1c\n\x0bStringValue\x12\r\n\x05value\x18\x01 \x01(\tb\x06proto3')
+
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'proto.wrappers_pb2', globals())
+if _descriptor._USE_C_DESCRIPTORS == False:
+
+ DESCRIPTOR._options = None
+ _DOUBLEVALUE._serialized_start=46
+ _DOUBLEVALUE._serialized_end=74
+ _FLOATVALUE._serialized_start=76
+ _FLOATVALUE._serialized_end=103
+ _INT64VALUE._serialized_start=105
+ _INT64VALUE._serialized_end=132
+ _UINT64VALUE._serialized_start=134
+ _UINT64VALUE._serialized_end=162
+ _INT32VALUE._serialized_start=164
+ _INT32VALUE._serialized_end=191
+ _UINT32VALUE._serialized_start=193
+ _UINT32VALUE._serialized_end=221
+ _BOOLVALUE._serialized_start=223
+ _BOOLVALUE._serialized_end=249
+ _STRINGVALUE._serialized_start=251
+ _STRINGVALUE._serialized_end=279
+# @@protoc_insertion_point(module_scope)
diff --git a/proto/wrappers_pb2.pyi b/proto/wrappers_pb2.pyi
new file mode 100644
index 00000000..57f539f5
--- /dev/null
+++ b/proto/wrappers_pb2.pyi
@@ -0,0 +1,212 @@
+"""
+@generated by mypy-protobuf. Do not edit manually!
+isort:skip_file
+Protocol Buffers - Google's data interchange format
+Copyright 2008 Google Inc. All rights reserved.
+https://developers.google.com/protocol-buffers/
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+"""
+import builtins
+import google.protobuf.descriptor
+import google.protobuf.message
+import sys
+
+if sys.version_info >= (3, 8):
+ import typing as typing_extensions
+else:
+ import typing_extensions
+
+DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
+
+@typing_extensions.final
+class DoubleValue(google.protobuf.message.Message):
+ """Wrapper message for `double`.
+
+ The JSON representation for `DoubleValue` is JSON number.
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ VALUE_FIELD_NUMBER: builtins.int
+ value: builtins.float
+ """The double value."""
+ def __init__(
+ self,
+ *,
+ value: builtins.float = ...,
+ ) -> None: ...
+ def ClearField(self, field_name: typing_extensions.Literal["value", b"value"]) -> None: ...
+
+global___DoubleValue = DoubleValue
+
+@typing_extensions.final
+class FloatValue(google.protobuf.message.Message):
+ """Wrapper message for `float`.
+
+ The JSON representation for `FloatValue` is JSON number.
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ VALUE_FIELD_NUMBER: builtins.int
+ value: builtins.float
+ """The float value."""
+ def __init__(
+ self,
+ *,
+ value: builtins.float = ...,
+ ) -> None: ...
+ def ClearField(self, field_name: typing_extensions.Literal["value", b"value"]) -> None: ...
+
+global___FloatValue = FloatValue
+
+@typing_extensions.final
+class Int64Value(google.protobuf.message.Message):
+ """Wrapper message for `int64`.
+
+ The JSON representation for `Int64Value` is JSON string.
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ VALUE_FIELD_NUMBER: builtins.int
+ value: builtins.int
+ """The int64 value."""
+ def __init__(
+ self,
+ *,
+ value: builtins.int = ...,
+ ) -> None: ...
+ def ClearField(self, field_name: typing_extensions.Literal["value", b"value"]) -> None: ...
+
+global___Int64Value = Int64Value
+
+@typing_extensions.final
+class UInt64Value(google.protobuf.message.Message):
+ """Wrapper message for `uint64`.
+
+ The JSON representation for `UInt64Value` is JSON string.
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ VALUE_FIELD_NUMBER: builtins.int
+ value: builtins.int
+ """The uint64 value."""
+ def __init__(
+ self,
+ *,
+ value: builtins.int = ...,
+ ) -> None: ...
+ def ClearField(self, field_name: typing_extensions.Literal["value", b"value"]) -> None: ...
+
+global___UInt64Value = UInt64Value
+
+@typing_extensions.final
+class Int32Value(google.protobuf.message.Message):
+ """Wrapper message for `int32`.
+
+ The JSON representation for `Int32Value` is JSON number.
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ VALUE_FIELD_NUMBER: builtins.int
+ value: builtins.int
+ """The int32 value."""
+ def __init__(
+ self,
+ *,
+ value: builtins.int = ...,
+ ) -> None: ...
+ def ClearField(self, field_name: typing_extensions.Literal["value", b"value"]) -> None: ...
+
+global___Int32Value = Int32Value
+
+@typing_extensions.final
+class UInt32Value(google.protobuf.message.Message):
+ """Wrapper message for `uint32`.
+
+ The JSON representation for `UInt32Value` is JSON number.
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ VALUE_FIELD_NUMBER: builtins.int
+ value: builtins.int
+ """The uint32 value."""
+ def __init__(
+ self,
+ *,
+ value: builtins.int = ...,
+ ) -> None: ...
+ def ClearField(self, field_name: typing_extensions.Literal["value", b"value"]) -> None: ...
+
+global___UInt32Value = UInt32Value
+
+@typing_extensions.final
+class BoolValue(google.protobuf.message.Message):
+ """Wrapper message for `bool`.
+
+ The JSON representation for `BoolValue` is JSON `true` and `false`.
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ VALUE_FIELD_NUMBER: builtins.int
+ value: builtins.bool
+ """The bool value."""
+ def __init__(
+ self,
+ *,
+ value: builtins.bool = ...,
+ ) -> None: ...
+ def ClearField(self, field_name: typing_extensions.Literal["value", b"value"]) -> None: ...
+
+global___BoolValue = BoolValue
+
+@typing_extensions.final
+class StringValue(google.protobuf.message.Message):
+ """Wrapper message for `string`.
+
+ The JSON representation for `StringValue` is JSON string.
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ VALUE_FIELD_NUMBER: builtins.int
+ value: builtins.str
+ """The string value."""
+ def __init__(
+ self,
+ *,
+ value: builtins.str = ...,
+ ) -> None: ...
+ def ClearField(self, field_name: typing_extensions.Literal["value", b"value"]) -> None: ...
+
+global___StringValue = StringValue
diff --git a/src/debug.c b/src/debug.c
index 2877a8b3..a3f515a6 100644
--- a/src/debug.c
+++ b/src/debug.c
@@ -1,15 +1,14 @@
-#include "os.h"
#include "debug.h"
+#include "os.h"
+
// This symbol is defined by the link script to be at the start of the stack
// area.
extern unsigned long app_stack_canary;
-#define STACK_CANARY (*((volatile uint32_t*) &app_stack_canary))
+#define STACK_CANARY (*((volatile uint32_t *)&app_stack_canary))
-void debug_init_stack_canary() {
- STACK_CANARY = 0xDEADBEEF;
-}
+void debug_init_stack_canary() { STACK_CANARY = 0xDEADBEEF; }
void debug_check_stack_canary() {
if (STACK_CANARY != 0xDEADBEEF) {
@@ -17,6 +16,4 @@ void debug_check_stack_canary() {
}
}
-uint32_t debug_get_stack_canary() {
- return STACK_CANARY;
-}
+uint32_t debug_get_stack_canary() { return STACK_CANARY; }
diff --git a/src/get_app_configuration.c b/src/get_app_configuration.c
index 9d63f358..6c784fd8 100644
--- a/src/get_app_configuration.c
+++ b/src/get_app_configuration.c
@@ -1,19 +1,14 @@
-#include
-
#include
#include
+#include
#include "errors.h"
#include "io.h"
void handle_get_app_configuration(
- uint8_t p1,
- uint8_t p2,
- const uint8_t* const buffer,
- uint16_t len,
- /* out */ volatile const unsigned int* const flags,
- /* out */ volatile const unsigned int* const tx
-) {
+ uint8_t p1, uint8_t p2, const uint8_t *const buffer, uint16_t len,
+ /* out */ volatile const unsigned int *const flags,
+ /* out */ volatile const unsigned int *const tx) {
UNUSED(p1);
UNUSED(p2);
UNUSED(buffer);
@@ -22,12 +17,12 @@ void handle_get_app_configuration(
UNUSED(tx);
// storage allowed?
- G_io_apdu_buffer[0] = 0;
+ G_io_apdu_buffer[ 0 ] = 0;
// version
- G_io_apdu_buffer[1] = APPVERSION_M;
- G_io_apdu_buffer[2] = APPVERSION_N;
- G_io_apdu_buffer[3] = APPVERSION_P;
+ G_io_apdu_buffer[ 1 ] = APPVERSION_M;
+ G_io_apdu_buffer[ 2 ] = APPVERSION_N;
+ G_io_apdu_buffer[ 3 ] = APPVERSION_P;
io_exchange_with_code(EXCEPTION_OK, 4);
}
diff --git a/src/get_public_key.c b/src/get_public_key.c
index 4f28796d..b9c43829 100644
--- a/src/get_public_key.c
+++ b/src/get_public_key.c
@@ -1,46 +1,44 @@
+#include "get_public_key.h"
+
#include
#include
#include
-#include "globals.h"
-#include "printf.h"
-#include "globals.h"
#include "debug.h"
#include "errors.h"
+#include "globals.h"
#include "handlers.h"
#include "hedera.h"
#include "io.h"
-#include "utils.h"
+#include "printf.h"
#include "ui.h"
-#include "get_public_key.h"
+#include "utils.h"
static struct get_public_key_context_t {
uint32_t key_index;
// Lines on the UI Screen
- char ui_approve_l2[DISPLAY_SIZE + 1];
+ char ui_approve_l2[ DISPLAY_SIZE + 1 ];
cx_ecfp_public_key_t public;
// Public Key Compare
uint8_t display_index;
- uint8_t full_key[KEY_SIZE + 1];
- uint8_t partial_key[DISPLAY_SIZE + 1];
+ uint8_t full_key[ KEY_SIZE + 1 ];
+ uint8_t partial_key[ DISPLAY_SIZE + 1 ];
} ctx;
#if defined(TARGET_NANOS)
static const bagl_element_t ui_get_public_key_compare[] = {
- UI_BACKGROUND(),
- UI_ICON_LEFT(LEFT_ICON_ID, BAGL_GLYPH_ICON_LEFT),
+ UI_BACKGROUND(), UI_ICON_LEFT(LEFT_ICON_ID, BAGL_GLYPH_ICON_LEFT),
UI_ICON_RIGHT(RIGHT_ICON_ID, BAGL_GLYPH_ICON_RIGHT),
// <= =>
// Public Key
//
//
UI_TEXT(LINE_1_ID, 0, 12, 128, "Public Key"),
- UI_TEXT(LINE_2_ID, 0, 26, 128, ctx.partial_key)
-};
+ UI_TEXT(LINE_2_ID, 0, 26, 128, ctx.partial_key)};
static const bagl_element_t ui_get_public_key_approve[] = {
UI_BACKGROUND(),
@@ -55,17 +53,11 @@ static const bagl_element_t ui_get_public_key_approve[] = {
};
void shift_partial_key() {
- memmove(
- ctx.partial_key,
- ctx.full_key + ctx.display_index,
- DISPLAY_SIZE
- );
+ memmove(ctx.partial_key, ctx.full_key + ctx.display_index, DISPLAY_SIZE);
}
static unsigned int ui_get_public_key_compare_button(
- unsigned int button_mask,
- unsigned int button_mask_counter
-) {
+ unsigned int button_mask, unsigned int button_mask_counter) {
UNUSED(button_mask_counter);
switch (button_mask) {
case BUTTON_LEFT: // Left
@@ -76,7 +68,8 @@ static unsigned int ui_get_public_key_compare_button(
break;
case BUTTON_RIGHT: // Right
case BUTTON_EVT_FAST | BUTTON_RIGHT:
- if (ctx.display_index < KEY_SIZE - DISPLAY_SIZE) ctx.display_index++;
+ if (ctx.display_index < KEY_SIZE - DISPLAY_SIZE)
+ ctx.display_index++;
shift_partial_key();
UX_REDISPLAY();
break;
@@ -87,14 +80,12 @@ static unsigned int ui_get_public_key_compare_button(
return 0;
}
-static const bagl_element_t* ui_prepro_get_public_key_compare(
- const bagl_element_t* element
-) {
- if (element->component.userid == LEFT_ICON_ID
- && ctx.display_index == 0)
+static const bagl_element_t *ui_prepro_get_public_key_compare(
+ const bagl_element_t *element) {
+ if (element->component.userid == LEFT_ICON_ID && ctx.display_index == 0)
return NULL; // Hide Left Arrow at Left Edge
- if (element->component.userid == RIGHT_ICON_ID
- && ctx.display_index == KEY_SIZE - DISPLAY_SIZE)
+ if (element->component.userid == RIGHT_ICON_ID &&
+ ctx.display_index == KEY_SIZE - DISPLAY_SIZE)
return NULL; // Hide Right Arrow at Right Edge
return element;
}
@@ -102,22 +93,17 @@ static const bagl_element_t* ui_prepro_get_public_key_compare(
void compare_pk() {
// init partial key str from full str
memmove(ctx.partial_key, ctx.full_key, DISPLAY_SIZE);
- ctx.partial_key[DISPLAY_SIZE] = '\0';
-
+ ctx.partial_key[ DISPLAY_SIZE ] = '\0';
+
// init display index
ctx.display_index = 0;
// Display compare with button mask
- UX_DISPLAY(
- ui_get_public_key_compare,
- ui_prepro_get_public_key_compare
- );
+ UX_DISPLAY(ui_get_public_key_compare, ui_prepro_get_public_key_compare);
}
static unsigned int ui_get_public_key_approve_button(
- unsigned int button_mask,
- unsigned int button_mask_counter
-) {
+ unsigned int button_mask, unsigned int button_mask_counter) {
UNUSED(button_mask_counter);
switch (button_mask) {
case BUTTON_EVT_RELEASED | BUTTON_LEFT: // REJECT
@@ -145,65 +131,30 @@ unsigned int io_seproxyhal_touch_pk_ok(const bagl_element_t *e) {
}
unsigned int io_seproxyhal_touch_pk_cancel(const bagl_element_t *e) {
- io_exchange_with_code(EXCEPTION_USER_REJECTED, 0);
- ui_idle();
- return 0;
+ io_exchange_with_code(EXCEPTION_USER_REJECTED, 0);
+ ui_idle();
+ return 0;
}
-UX_STEP_NOCB(
- ux_approve_pk_flow_1_step,
- bn,
- {
- "Export Public",
- ctx.ui_approve_l2
- }
-);
-
-UX_STEP_VALID(
- ux_approve_pk_flow_2_step,
- pb,
- io_seproxyhal_touch_pk_ok(NULL),
- {
- &C_icon_validate_14,
- "Approve"
- }
-);
-
-UX_STEP_VALID(
- ux_approve_pk_flow_3_step,
- pb,
- io_seproxyhal_touch_pk_cancel(NULL),
- {
- &C_icon_crossmark,
- "Reject"
- }
-);
-
-UX_STEP_CB(
- ux_compare_pk_flow_1_step,
- bnnn_paging,
- ui_idle(),
- {
- .title = "Public Key",
- .text = (char*) ctx.full_key
- }
-);
+UX_STEP_NOCB(ux_approve_pk_flow_1_step, bn,
+ {"Export Public", ctx.ui_approve_l2});
-UX_DEF(
- ux_approve_pk_flow,
- &ux_approve_pk_flow_1_step,
- &ux_approve_pk_flow_2_step,
- &ux_approve_pk_flow_3_step
-);
+UX_STEP_VALID(ux_approve_pk_flow_2_step, pb, io_seproxyhal_touch_pk_ok(NULL),
+ {&C_icon_validate_14, "Approve"});
-UX_DEF(
- ux_compare_pk_flow,
- &ux_compare_pk_flow_1_step
-);
+UX_STEP_VALID(ux_approve_pk_flow_3_step, pb,
+ io_seproxyhal_touch_pk_cancel(NULL),
+ {&C_icon_crossmark, "Reject"});
-void compare_pk() {
- ux_flow_init(0, ux_compare_pk_flow, NULL);
-}
+UX_STEP_CB(ux_compare_pk_flow_1_step, bnnn_paging, ui_idle(),
+ {.title = "Public Key", .text = (char *)ctx.full_key});
+
+UX_DEF(ux_approve_pk_flow, &ux_approve_pk_flow_1_step,
+ &ux_approve_pk_flow_2_step, &ux_approve_pk_flow_3_step);
+
+UX_DEF(ux_compare_pk_flow, &ux_compare_pk_flow_1_step);
+
+void compare_pk() { ux_flow_init(0, ux_compare_pk_flow, NULL); }
#endif // TARGET
@@ -216,17 +167,13 @@ void get_pk() {
// Populate Key Hex String
bin2hex(ctx.full_key, G_io_apdu_buffer, KEY_SIZE);
- ctx.full_key[KEY_SIZE] = '\0';
+ ctx.full_key[ KEY_SIZE ] = '\0';
}
-void handle_get_public_key(
- uint8_t p1,
- uint8_t p2,
- uint8_t* buffer,
- uint16_t len,
- /* out */ volatile unsigned int* flags,
- /* out */ volatile unsigned int* tx
-) {
+void handle_get_public_key(uint8_t p1, uint8_t p2, uint8_t *buffer,
+ uint16_t len,
+ /* out */ volatile unsigned int *flags,
+ /* out */ volatile unsigned int *tx) {
UNUSED(p2);
UNUSED(len);
UNUSED(tx);
@@ -234,11 +181,12 @@ void handle_get_public_key(
// Read Key Index
ctx.key_index = U4LE(buffer, 0);
- // If p1 != 0, silent mode, for use by apps that request the user's public key frequently
- // Only do UI actions for p1 == 0
+ // If p1 != 0, silent mode, for use by apps that request the user's public
+ // key frequently Only do UI actions for p1 == 0
if (p1 == 0) {
// Complete "Export Public | Key #x?"
- hedera_snprintf(ctx.ui_approve_l2, DISPLAY_SIZE, "Key #%u?", ctx.key_index);
+ hedera_snprintf(ctx.ui_approve_l2, DISPLAY_SIZE, "Key #%u?",
+ ctx.key_index);
}
// Populate context with PK
diff --git a/src/get_public_key.h b/src/get_public_key.h
index c208e64d..c92c2e8a 100644
--- a/src/get_public_key.h
+++ b/src/get_public_key.h
@@ -9,20 +9,15 @@ void compare_pk();
void shift_partial_key();
static unsigned int ui_get_public_key_compare_button(
- unsigned int button_mask,
- unsigned int button_mask_counter
-);
+ unsigned int button_mask, unsigned int button_mask_counter);
-static const bagl_element_t* ui_prepro_get_public_key_compare(
- const bagl_element_t* element
-);
+static const bagl_element_t *ui_prepro_get_public_key_compare(
+ const bagl_element_t *element);
void send_pk();
static unsigned int ui_get_public_key_approve_button(
- unsigned int button_mask,
- unsigned int button_mask_counter
-);
+ unsigned int button_mask, unsigned int button_mask_counter);
#elif defined(TARGET_NANOX) || defined(TARGET_NANOS2)
diff --git a/src/handlers.h b/src/handlers.h
index 7d9842d6..8b4db662 100644
--- a/src/handlers.h
+++ b/src/handlers.h
@@ -9,14 +9,9 @@
#define INS_GET_PUBLIC_KEY 0x02
#define INS_SIGN_TRANSACTION 0x04
-typedef void handler_fn_t(
- uint8_t p1,
- uint8_t p2,
- uint8_t* buffer,
- uint16_t len,
- /* out */ volatile unsigned int* flags,
- /* out */ volatile unsigned int* tx
-);
+typedef void handler_fn_t(uint8_t p1, uint8_t p2, uint8_t* buffer, uint16_t len,
+ /* out */ volatile unsigned int* flags,
+ /* out */ volatile unsigned int* tx);
extern handler_fn_t handle_get_app_configuration;
extern handler_fn_t handle_get_public_key;
diff --git a/src/hedera.c b/src/hedera.c
index 3def8c08..36236760 100644
--- a/src/hedera.c
+++ b/src/hedera.c
@@ -1,60 +1,40 @@
-#include
+#include "hedera.h"
+
#include
+#include
#include
+
#include "globals.h"
-#include "hedera.h"
-bool hedera_derive_keypair(
- uint32_t index,
- /* out */ cx_ecfp_private_key_t* secret,
- /* out */ cx_ecfp_public_key_t* public
-) {
- static uint8_t seed[32];
- static uint32_t path[5];
+bool hedera_derive_keypair(uint32_t index,
+ /* out */ cx_ecfp_private_key_t *secret,
+ /* out */ cx_ecfp_public_key_t *public) {
+ static uint8_t seed[ 32 ];
+ static uint32_t path[ 5 ];
static cx_ecfp_private_key_t pk;
- path[0] = 44 | 0x80000000;
- path[1] = 3030 | 0x80000000;
- path[2] = 0x80000000;
- path[3] = 0x80000000;
- path[4] = index | 0x80000000;
-
- os_perso_derive_node_bip32_seed_key(
- HDW_ED25519_SLIP10,
- CX_CURVE_Ed25519,
- path,
- 5,
- seed,
- NULL,
- NULL,
- 0
- );
-
- if (CX_OK != cx_ecfp_init_private_key_no_throw(
- CX_CURVE_Ed25519,
- seed,
- sizeof(seed),
- &pk
- )) {
+ path[ 0 ] = 44 | 0x80000000;
+ path[ 1 ] = 3030 | 0x80000000;
+ path[ 2 ] = 0x80000000;
+ path[ 3 ] = 0x80000000;
+ path[ 4 ] = index | 0x80000000;
+
+ os_perso_derive_node_bip32_seed_key(HDW_ED25519_SLIP10, CX_CURVE_Ed25519,
+ path, 5, seed, NULL, NULL, 0);
+
+ if (CX_OK != cx_ecfp_init_private_key_no_throw(CX_CURVE_Ed25519, seed,
+ sizeof(seed), &pk)) {
return false;
}
if (public) {
- if (CX_OK != cx_ecfp_init_public_key_no_throw(
- CX_CURVE_Ed25519,
- NULL,
- 0,
- public
- )) {
+ if (CX_OK != cx_ecfp_init_public_key_no_throw(CX_CURVE_Ed25519, NULL, 0,
+ public)) {
return false;
}
- if (CX_OK != cx_ecfp_generate_pair_no_throw(
- CX_CURVE_Ed25519,
- public,
- &pk,
- 1
- )) {
+ if (CX_OK !=
+ cx_ecfp_generate_pair_no_throw(CX_CURVE_Ed25519, public, &pk, 1)) {
return false;
}
}
@@ -69,12 +49,8 @@ bool hedera_derive_keypair(
return true;
}
-bool hedera_sign(
- uint32_t index,
- const uint8_t* tx,
- uint8_t tx_len,
- /* out */ uint8_t* result
-) {
+bool hedera_sign(uint32_t index, const uint8_t *tx, uint8_t tx_len,
+ /* out */ uint8_t *result) {
static cx_ecfp_private_key_t pk;
// Get Keys
@@ -87,13 +63,13 @@ bool hedera_sign(
// Claims to want Hashes, but other apps use the message itself
// and complain that the documentation is wrong
if (CX_OK != cx_eddsa_sign_no_throw(
- &pk, // private key
- CX_SHA512, // hashID
- tx, // hash (really message)
- tx_len, // hash length (really message length)
- result, // signature
- 64 // signature length
- )) {
+ &pk, // private key
+ CX_SHA512, // hashID
+ tx, // hash (really message)
+ tx_len, // hash length (really message length)
+ result, // signature
+ 64 // signature length
+ )) {
return false;
}
diff --git a/src/hedera.h b/src/hedera.h
index 0800d9d0..984219d8 100644
--- a/src/hedera.h
+++ b/src/hedera.h
@@ -1,6 +1,7 @@
#ifndef LEDGER_HEDERA_HEDERA_H
#define LEDGER_HEDERA_HEDERA_H 1
+#include
#include
// Forward declare to avoid including os.h in a header file
@@ -9,18 +10,13 @@ struct cx_ecfp_256_private_key_s;
extern bool hedera_derive_keypair(
uint32_t index,
- /* out */ struct cx_ecfp_256_private_key_s* secret,
- /* out */ struct cx_ecfp_256_public_key_s* public
-);
+ /* out */ struct cx_ecfp_256_private_key_s *secret,
+ /* out */ struct cx_ecfp_256_public_key_s *public);
-extern bool hedera_sign(
- uint32_t index,
- const uint8_t* tx,
- uint8_t tx_len,
- /* out */ uint8_t* result
-);
+extern bool hedera_sign(uint32_t index, const uint8_t *tx, uint8_t tx_len,
+ /* out */ uint8_t *result);
-extern char* hedera_format_tinybar(uint64_t tinybar);
-extern char* hedera_format_amount(uint64_t amount, uint8_t decimals);
+extern char *hedera_format_tinybar(uint64_t tinybar);
+extern char *hedera_format_amount(uint64_t amount, uint8_t decimals);
#endif // LEDGER_HEDERA_HEDERA_H
diff --git a/src/hedera_format.c b/src/hedera_format.c
index c4443948..a50dcaee 100644
--- a/src/hedera_format.c
+++ b/src/hedera_format.c
@@ -1,7 +1,7 @@
-#include
-
#include "hedera_format.h"
+#include
+
char* hedera_format_tinybar(uint64_t tinybar) {
return hedera_format_amount(tinybar, 8);
}
@@ -9,7 +9,7 @@ char* hedera_format_tinybar(uint64_t tinybar) {
#define BUF_SIZE 32
char* hedera_format_amount(uint64_t amount, uint8_t decimals) {
- static char buf[BUF_SIZE];
+ static char buf[ BUF_SIZE ];
// NOTE: format of amounts are not sensitive
memset(buf, 0, BUF_SIZE);
@@ -17,8 +17,8 @@ char* hedera_format_amount(uint64_t amount, uint8_t decimals) {
// Quick shortcut if the amount is zero
// Regardless of decimals, the output is always "0"
if (amount == 0) {
- buf[0] = '0';
- buf[1] = '\0';
+ buf[ 0 ] = '0';
+ buf[ 1 ] = '\0';
return buf;
}
@@ -33,15 +33,15 @@ char* hedera_format_amount(uint64_t amount, uint8_t decimals) {
int digit = amount % 10;
amount /= 10;
- buf[i++] = '0' + digit;
+ buf[ i++ ] = '0' + digit;
if (i == decimals) {
- buf[i++] = '.';
+ buf[ i++ ] = '.';
}
}
- if (buf[i - 1] == '.') {
- buf[i++] = '0';
+ if (buf[ i - 1 ] == '.') {
+ buf[ i++ ] = '0';
}
int size = i;
@@ -51,17 +51,17 @@ char* hedera_format_amount(uint64_t amount, uint8_t decimals) {
while (j < i) {
i -= 1;
- tmp = buf[j];
- buf[j] = buf[i];
- buf[i] = tmp;
+ tmp = buf[ j ];
+ buf[ j ] = buf[ i ];
+ buf[ i ] = tmp;
j += 1;
}
for (j = size - 1; j > 0; j--) {
- if (buf[j] == '0') {
+ if (buf[ j ] == '0') {
continue;
- } else if (buf[j] == '.') {
+ } else if (buf[ j ] == '.') {
break;
} else {
j += 1;
@@ -70,7 +70,7 @@ char* hedera_format_amount(uint64_t amount, uint8_t decimals) {
}
if (j < size - 1) {
- buf[j] = '\0';
+ buf[ j ] = '\0';
}
return buf;
diff --git a/src/hedera_format.h b/src/hedera_format.h
index 06fb070a..18746c34 100644
--- a/src/hedera_format.h
+++ b/src/hedera_format.h
@@ -3,7 +3,7 @@
#include
-extern char* hedera_format_tinybar(uint64_t tinybar);
-extern char* hedera_format_amount(uint64_t amount, uint8_t decimals);
+extern char *hedera_format_tinybar(uint64_t tinybar);
+extern char *hedera_format_amount(uint64_t amount, uint8_t decimals);
#endif // LEDGER_HEDERA_HEDERA_FORMAT_H
diff --git a/src/io.c b/src/io.c
index 1d164801..22b348c4 100644
--- a/src/io.c
+++ b/src/io.c
@@ -1,17 +1,17 @@
+#include "debug.h"
#include "os.h"
-#include "ux.h"
#include "os_io_seproxyhal.h"
-#include "debug.h"
+#include "ux.h"
// Everything below this point is Ledger magic. And the magic isn't well-
// documented, so if you want to understand it, you'll need to read the
// source, which you can find in the sdk repo for your device.
// Fortunately, we are not meant to understand this.
-unsigned char G_io_seproxyhal_spi_buffer[IO_SEPROXYHAL_BUFFER_SIZE_B];
+unsigned char G_io_seproxyhal_spi_buffer[ IO_SEPROXYHAL_BUFFER_SIZE_B ];
void io_seproxyhal_display(const bagl_element_t *element) {
- io_seproxyhal_display_default((bagl_element_t*)element);
+ io_seproxyhal_display_default((bagl_element_t *)element);
}
unsigned char io_event(unsigned char channel) {
@@ -21,7 +21,7 @@ unsigned char io_event(unsigned char channel) {
debug_check_stack_canary();
// can't have more than one tag in the reply, not supported yet.
- switch (G_io_seproxyhal_spi_buffer[0]) {
+ switch (G_io_seproxyhal_spi_buffer[ 0 ]) {
case SEPROXYHAL_TAG_FINGER_EVENT:
UX_FINGER_EVENT(G_io_seproxyhal_spi_buffer);
break;
@@ -31,7 +31,9 @@ unsigned char io_event(unsigned char channel) {
break;
case SEPROXYHAL_TAG_STATUS_EVENT:
- if (G_io_apdu_media == IO_APDU_MEDIA_USB_HID && !(U4BE(G_io_seproxyhal_spi_buffer, 3) & SEPROXYHAL_TAG_STATUS_EVENT_FLAG_USB_POWERED)) {
+ if (G_io_apdu_media == IO_APDU_MEDIA_USB_HID &&
+ !(U4BE(G_io_seproxyhal_spi_buffer, 3) &
+ SEPROXYHAL_TAG_STATUS_EVENT_FLAG_USB_POWERED)) {
THROW(EXCEPTION_IO_RESET);
}
@@ -65,7 +67,8 @@ unsigned short io_exchange_al(unsigned char channel, unsigned short tx_len) {
case CHANNEL_KEYBOARD:
break;
- // multiplexed io exchange over a SPI channel and TLV encapsulated protocol
+ // multiplexed io exchange over a SPI channel and TLV encapsulated
+ // protocol
case CHANNEL_SPI:
if (tx_len) {
io_seproxyhal_spi_send(G_io_apdu_buffer, tx_len);
@@ -74,10 +77,10 @@ unsigned short io_exchange_al(unsigned char channel, unsigned short tx_len) {
reset();
}
return 0; // nothing received from the master so far (it's a tx
- // transaction)
+ // transaction)
} else {
return io_seproxyhal_spi_recv(G_io_apdu_buffer,
- sizeof(G_io_apdu_buffer), 0);
+ sizeof(G_io_apdu_buffer), 0);
}
default:
@@ -87,8 +90,8 @@ unsigned short io_exchange_al(unsigned char channel, unsigned short tx_len) {
}
void io_exchange_with_code(uint16_t code, uint16_t tx) {
- G_io_apdu_buffer[tx++] = code >> 8;
- G_io_apdu_buffer[tx++] = code & 0xff;
+ G_io_apdu_buffer[ tx++ ] = code >> 8;
+ G_io_apdu_buffer[ tx++ ] = code & 0xff;
io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, tx);
}
diff --git a/src/io.h b/src/io.h
index c983b93f..71754812 100644
--- a/src/io.h
+++ b/src/io.h
@@ -1,9 +1,9 @@
#ifndef LEDGER_HEDERA_IO_H
#define LEDGER_HEDERA_IO_H 1
-#include
#include
#include
+#include
extern void io_exchange_with_code(uint16_t code, uint16_t tx);
diff --git a/src/main.c b/src/main.c
index f632ea12..3bdc008c 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,10 +1,10 @@
+#include "debug.h"
#include "errors.h"
+#include "globals.h"
#include "handlers.h"
-#include "ui.h"
#include "io.h"
+#include "ui.h"
#include "utils.h"
-#include "debug.h"
-#include "globals.h"
// This is the main loop that reads and writes APDUs. It receives request
// APDUs from the computer, looks up the corresponding command handler, and
@@ -28,7 +28,8 @@ void app_main() {
BEGIN_TRY {
TRY {
rx = tx;
- tx = 0; // ensure no race in catch_other if io_exchange throws an error
+ tx = 0; // ensure no race in catch_other if io_exchange throws
+ // an error
rx = io_exchange(CHANNEL_APDU | flags, rx);
flags = 0;
@@ -38,55 +39,44 @@ void app_main() {
}
// malformed APDU
- if (G_io_apdu_buffer[OFFSET_CLA] != CLA) {
+ if (G_io_apdu_buffer[ OFFSET_CLA ] != CLA) {
THROW(EXCEPTION_MALFORMED_APDU);
}
// APDU handler functions defined in handlers
- switch (G_io_apdu_buffer[OFFSET_INS]) {
+ switch (G_io_apdu_buffer[ OFFSET_INS ]) {
case INS_GET_APP_CONFIGURATION:
// handlers -> get_app_configuration
handle_get_app_configuration(
- G_io_apdu_buffer[OFFSET_P1],
- G_io_apdu_buffer[OFFSET_P2],
- G_io_apdu_buffer + OFFSET_CDATA,
- G_io_apdu_buffer[OFFSET_LC],
- &flags,
- &tx
- );
+ G_io_apdu_buffer[ OFFSET_P1 ],
+ G_io_apdu_buffer[ OFFSET_P2 ],
+ G_io_apdu_buffer + OFFSET_CDATA,
+ G_io_apdu_buffer[ OFFSET_LC ], &flags, &tx);
break;
case INS_GET_PUBLIC_KEY:
// handlers -> get_public_key
- handle_get_public_key(
- G_io_apdu_buffer[OFFSET_P1],
- G_io_apdu_buffer[OFFSET_P2],
- G_io_apdu_buffer + OFFSET_CDATA,
- G_io_apdu_buffer[OFFSET_LC],
- &flags,
- &tx
- );
+ handle_get_public_key(G_io_apdu_buffer[ OFFSET_P1 ],
+ G_io_apdu_buffer[ OFFSET_P2 ],
+ G_io_apdu_buffer + OFFSET_CDATA,
+ G_io_apdu_buffer[ OFFSET_LC ],
+ &flags, &tx);
break;
case INS_SIGN_TRANSACTION:
// handlers -> sign_transaction
- handle_sign_transaction(
- G_io_apdu_buffer[OFFSET_P1],
- G_io_apdu_buffer[OFFSET_P2],
- G_io_apdu_buffer + OFFSET_CDATA,
- G_io_apdu_buffer[OFFSET_LC],
- &flags,
- &tx
- );
+ handle_sign_transaction(G_io_apdu_buffer[ OFFSET_P1 ],
+ G_io_apdu_buffer[ OFFSET_P2 ],
+ G_io_apdu_buffer + OFFSET_CDATA,
+ G_io_apdu_buffer[ OFFSET_LC ],
+ &flags, &tx);
break;
- default:
+ default:
THROW(EXCEPTION_UNKNOWN_INS);
}
}
- CATCH(EXCEPTION_IO_RESET) {
- THROW(EXCEPTION_IO_RESET);
- }
+ CATCH(EXCEPTION_IO_RESET) { THROW(EXCEPTION_IO_RESET); }
CATCH_OTHER(e) {
// Convert exception to response code and add to APDU return
switch (e & 0xF000) {
@@ -100,8 +90,8 @@ void app_main() {
break;
}
- G_io_apdu_buffer[tx++] = sw >> 8;
- G_io_apdu_buffer[tx++] = sw & 0xff;
+ G_io_apdu_buffer[ tx++ ] = sw >> 8;
+ G_io_apdu_buffer[ tx++ ] = sw & 0xff;
}
FINALLY {
// explicitly do nothing
@@ -114,9 +104,7 @@ void app_main() {
void app_exit(void) {
// All os calls must be wrapped in a try catch context
BEGIN_TRY_L(exit) {
- TRY_L(exit) {
- os_sched_exit(-1);
- }
+ TRY_L(exit) { os_sched_exit(-1); }
FINALLY_L(exit) {
// explicitly do nothing
}
@@ -139,13 +127,14 @@ __attribute__((section(".boot"))) int main() {
BEGIN_TRY {
TRY {
- // Initialize the hardware abstraction layer (HAL) in
+ // Initialize the hardware abstraction layer (HAL) in
// the Ledger SDK
io_seproxyhal_init();
#ifdef TARGET_NANOX
// grab the current plane mode setting
- G_io_app.plane_mode = os_setting_get(OS_SETTING_PLANEMODE, NULL, 0);
+ G_io_app.plane_mode =
+ os_setting_get(OS_SETTING_PLANEMODE, NULL, 0);
#endif // TARGET_NANOX
#ifdef HAVE_BLE
@@ -166,9 +155,7 @@ __attribute__((section(".boot"))) int main() {
// reset IO and UX before continuing
continue;
}
- CATCH_ALL {
- break;
- }
+ CATCH_ALL { break; }
FINALLY {
// explicitly do nothing
}
diff --git a/src/printf.c b/src/printf.c
index 285615dd..6a43da10 100644
--- a/src/printf.c
+++ b/src/printf.c
@@ -22,19 +22,19 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
-// \brief Tiny printf, sprintf and (v)snprintf implementation, optimized for speed on
-// embedded systems with a very limited resources. These routines are thread
-// safe and reentrant!
-// Use this instead of the bloated standard/newlib printf cause these use
-// malloc for printf (and may not be thread safe).
+// \brief Tiny printf, sprintf and (v)snprintf implementation, optimized for
+// speed on
+// embedded systems with a very limited resources. These routines are
+// thread safe and reentrant! Use this instead of the bloated
+// standard/newlib printf cause these use malloc for printf (and may not
+// be thread safe).
//
///////////////////////////////////////////////////////////////////////////////
-#include
-#include
-
#include "printf.h"
+#include
+#include
// define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the
// printf_config.h header file
@@ -43,19 +43,18 @@
#include "printf_config.h"
#endif
-
// 'ntoa' conversion buffer size, this must be big enough to hold one converted
// numeric number including padded zeros (dynamically created on stack)
// default: 32 byte
#ifndef PRINTF_NTOA_BUFFER_SIZE
-#define PRINTF_NTOA_BUFFER_SIZE 32U
+#define PRINTF_NTOA_BUFFER_SIZE 32U
#endif
// 'ftoa' conversion buffer size, this must be big enough to hold one converted
// float number including padded zeros (dynamically created on stack)
// default: 32 byte
#ifndef PRINTF_FTOA_BUFFER_SIZE
-#define PRINTF_FTOA_BUFFER_SIZE 32U
+#define PRINTF_FTOA_BUFFER_SIZE 32U
#endif
// support for the floating point type (%f)
@@ -73,13 +72,13 @@
// define the default floating point precision
// default: 6 digits
#ifndef PRINTF_DEFAULT_FLOAT_PRECISION
-#define PRINTF_DEFAULT_FLOAT_PRECISION 6U
+#define PRINTF_DEFAULT_FLOAT_PRECISION 6U
#endif
// define the largest float suitable to print with %f
// default: 1e9
#ifndef PRINTF_MAX_FLOAT
-#define PRINTF_MAX_FLOAT 1e9
+#define PRINTF_MAX_FLOAT 1e9
#endif
// support for the long long types (%llu or %p)
@@ -98,817 +97,872 @@
///////////////////////////////////////////////////////////////////////////////
// internal flag definitions
-#define FLAGS_ZEROPAD (1U << 0U)
-#define FLAGS_LEFT (1U << 1U)
-#define FLAGS_PLUS (1U << 2U)
-#define FLAGS_SPACE (1U << 3U)
-#define FLAGS_HASH (1U << 4U)
-#define FLAGS_UPPERCASE (1U << 5U)
-#define FLAGS_CHAR (1U << 6U)
-#define FLAGS_SHORT (1U << 7U)
-#define FLAGS_LONG (1U << 8U)
-#define FLAGS_LONG_LONG (1U << 9U)
+#define FLAGS_ZEROPAD (1U << 0U)
+#define FLAGS_LEFT (1U << 1U)
+#define FLAGS_PLUS (1U << 2U)
+#define FLAGS_SPACE (1U << 3U)
+#define FLAGS_HASH (1U << 4U)
+#define FLAGS_UPPERCASE (1U << 5U)
+#define FLAGS_CHAR (1U << 6U)
+#define FLAGS_SHORT (1U << 7U)
+#define FLAGS_LONG (1U << 8U)
+#define FLAGS_LONG_LONG (1U << 9U)
#define FLAGS_PRECISION (1U << 10U)
#define FLAGS_ADAPT_EXP (1U << 11U)
-
// import float.h for DBL_MAX
#if defined(PRINTF_SUPPORT_FLOAT)
#include
#endif
-
// output function type
-typedef void (*out_fct_type)(char character, void* buffer, size_t idx, size_t maxlen);
-
+typedef void (*out_fct_type)(char character, void* buffer, size_t idx,
+ size_t maxlen);
// wrapper (used as buffer) for output function type
typedef struct {
- void (*fct)(char character, void* arg);
- void* arg;
+ void (*fct)(char character, void* arg);
+ void* arg;
} out_fct_wrap_type;
-
// internal buffer output
-static inline void _out_buffer(char character, void* buffer, size_t idx, size_t maxlen)
-{
- if (idx < maxlen) {
- ((char*)buffer)[idx] = character;
- }
+static inline void _out_buffer(char character, void* buffer, size_t idx,
+ size_t maxlen) {
+ if (idx < maxlen) {
+ ((char*)buffer)[ idx ] = character;
+ }
}
-
// internal null output
-static inline void _out_null(char character, void* buffer, size_t idx, size_t maxlen)
-{
- (void)character; (void)buffer; (void)idx; (void)maxlen;
+static inline void _out_null(char character, void* buffer, size_t idx,
+ size_t maxlen) {
+ (void)character;
+ (void)buffer;
+ (void)idx;
+ (void)maxlen;
}
-
// internal _putchar wrapper
-static inline void _out_char(char character, void* buffer, size_t idx, size_t maxlen)
-{
- (void)buffer; (void)idx; (void)maxlen;
- if (character) {
- _putchar(character);
- }
+static inline void _out_char(char character, void* buffer, size_t idx,
+ size_t maxlen) {
+ (void)buffer;
+ (void)idx;
+ (void)maxlen;
+ if (character) {
+ _putchar(character);
+ }
}
-
// internal output function wrapper
-static inline void _out_fct(char character, void* buffer, size_t idx, size_t maxlen)
-{
- (void)idx; (void)maxlen;
- if (character) {
- // buffer is the output fct pointer
- ((out_fct_wrap_type*)buffer)->fct(character, ((out_fct_wrap_type*)buffer)->arg);
- }
+static inline void _out_fct(char character, void* buffer, size_t idx,
+ size_t maxlen) {
+ (void)idx;
+ (void)maxlen;
+ if (character) {
+ // buffer is the output fct pointer
+ ((out_fct_wrap_type*)buffer)
+ ->fct(character, ((out_fct_wrap_type*)buffer)->arg);
+ }
}
-
// internal secure strlen
-// \return The length of the string (excluding the terminating 0) limited by 'maxsize'
-static inline unsigned int _strnlen_s(const char* str, size_t maxsize)
-{
- const char* s;
- for (s = str; *s && maxsize--; ++s);
- return (unsigned int)(s - str);
+// \return The length of the string (excluding the terminating 0) limited by
+// 'maxsize'
+static inline unsigned int _strnlen_s(const char* str, size_t maxsize) {
+ const char* s;
+ for (s = str; *s && maxsize--; ++s)
+ ;
+ return (unsigned int)(s - str);
}
-
// internal test if char is a digit (0-9)
// \return true if char is a digit
-static inline bool _is_digit(char ch)
-{
- return (ch >= '0') && (ch <= '9');
-}
-
+static inline bool _is_digit(char ch) { return (ch >= '0') && (ch <= '9'); }
// internal ASCII string to unsigned int conversion
-static unsigned int _atoi(const char** str)
-{
- unsigned int i = 0U;
- while (_is_digit(**str)) {
- i = i * 10U + (unsigned int)(*((*str)++) - '0');
- }
- return i;
+static unsigned int _atoi(const char** str) {
+ unsigned int i = 0U;
+ while (_is_digit(**str)) {
+ i = i * 10U + (unsigned int)(*((*str)++) - '0');
+ }
+ return i;
}
-
// output the specified string in reverse, taking care of any zero-padding
-static size_t _out_rev(out_fct_type out, char* buffer, size_t idx, size_t maxlen, const char* buf, size_t len, unsigned int width, unsigned int flags)
-{
- const size_t start_idx = idx;
-
- // pad spaces up to given width
- if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) {
- for (size_t i = len; i < width; i++) {
- out(' ', buffer, idx++, maxlen);
+static size_t _out_rev(out_fct_type out, char* buffer, size_t idx,
+ size_t maxlen, const char* buf, size_t len,
+ unsigned int width, unsigned int flags) {
+ const size_t start_idx = idx;
+
+ // pad spaces up to given width
+ if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) {
+ for (size_t i = len; i < width; i++) {
+ out(' ', buffer, idx++, maxlen);
+ }
}
- }
- // reverse string
- while (len) {
- out(buf[--len], buffer, idx++, maxlen);
- }
+ // reverse string
+ while (len) {
+ out(buf[ --len ], buffer, idx++, maxlen);
+ }
- // append pad spaces up to given width
- if (flags & FLAGS_LEFT) {
- while (idx - start_idx < width) {
- out(' ', buffer, idx++, maxlen);
+ // append pad spaces up to given width
+ if (flags & FLAGS_LEFT) {
+ while (idx - start_idx < width) {
+ out(' ', buffer, idx++, maxlen);
+ }
}
- }
- return idx;
+ return idx;
}
-
// internal itoa format
-static size_t _ntoa_format(out_fct_type out, char* buffer, size_t idx, size_t maxlen, char* buf, size_t len, bool negative, unsigned int base, unsigned int prec, unsigned int width, unsigned int flags)
-{
- // pad leading zeros
- if (!(flags & FLAGS_LEFT)) {
- if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
- width--;
- }
- while ((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
- buf[len++] = '0';
- }
- while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
- buf[len++] = '0';
+static size_t _ntoa_format(out_fct_type out, char* buffer, size_t idx,
+ size_t maxlen, char* buf, size_t len, bool negative,
+ unsigned int base, unsigned int prec,
+ unsigned int width, unsigned int flags) {
+ // pad leading zeros
+ if (!(flags & FLAGS_LEFT)) {
+ if (width && (flags & FLAGS_ZEROPAD) &&
+ (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
+ width--;
+ }
+ while ((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
+ buf[ len++ ] = '0';
+ }
+ while ((flags & FLAGS_ZEROPAD) && (len < width) &&
+ (len < PRINTF_NTOA_BUFFER_SIZE)) {
+ buf[ len++ ] = '0';
+ }
}
- }
- // handle hash
- if (flags & FLAGS_HASH) {
- if (!(flags & FLAGS_PRECISION) && len && ((len == prec) || (len == width))) {
- len--;
- if (len && (base == 16U)) {
- len--;
- }
- }
- if ((base == 16U) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
- buf[len++] = 'x';
- }
- else if ((base == 16U) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
- buf[len++] = 'X';
- }
- else if ((base == 2U) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
- buf[len++] = 'b';
- }
- if (len < PRINTF_NTOA_BUFFER_SIZE) {
- buf[len++] = '0';
+ // handle hash
+ if (flags & FLAGS_HASH) {
+ if (!(flags & FLAGS_PRECISION) && len &&
+ ((len == prec) || (len == width))) {
+ len--;
+ if (len && (base == 16U)) {
+ len--;
+ }
+ }
+ if ((base == 16U) && !(flags & FLAGS_UPPERCASE) &&
+ (len < PRINTF_NTOA_BUFFER_SIZE)) {
+ buf[ len++ ] = 'x';
+ } else if ((base == 16U) && (flags & FLAGS_UPPERCASE) &&
+ (len < PRINTF_NTOA_BUFFER_SIZE)) {
+ buf[ len++ ] = 'X';
+ } else if ((base == 2U) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
+ buf[ len++ ] = 'b';
+ }
+ if (len < PRINTF_NTOA_BUFFER_SIZE) {
+ buf[ len++ ] = '0';
+ }
}
- }
- if (len < PRINTF_NTOA_BUFFER_SIZE) {
- if (negative) {
- buf[len++] = '-';
- }
- else if (flags & FLAGS_PLUS) {
- buf[len++] = '+'; // ignore the space if the '+' exists
- }
- else if (flags & FLAGS_SPACE) {
- buf[len++] = ' ';
+ if (len < PRINTF_NTOA_BUFFER_SIZE) {
+ if (negative) {
+ buf[ len++ ] = '-';
+ } else if (flags & FLAGS_PLUS) {
+ buf[ len++ ] = '+'; // ignore the space if the '+' exists
+ } else if (flags & FLAGS_SPACE) {
+ buf[ len++ ] = ' ';
+ }
}
- }
- return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);
+ return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);
}
-
// internal itoa for 'long' type
-static size_t _ntoa_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long value, bool negative, unsigned long base, unsigned int prec, unsigned int width, unsigned int flags)
-{
- char buf[PRINTF_NTOA_BUFFER_SIZE];
- size_t len = 0U;
-
- // no hash for 0 values
- if (!value) {
- flags &= ~FLAGS_HASH;
- }
-
- // write if precision != 0 and value is != 0
- if (!(flags & FLAGS_PRECISION) || value) {
- do {
- const char digit = (char)(value % base);
- buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
- value /= base;
- } while (value && (len < PRINTF_NTOA_BUFFER_SIZE));
- }
-
- return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags);
+static size_t _ntoa_long(out_fct_type out, char* buffer, size_t idx,
+ size_t maxlen, unsigned long value, bool negative,
+ unsigned long base, unsigned int prec,
+ unsigned int width, unsigned int flags) {
+ char buf[ PRINTF_NTOA_BUFFER_SIZE ];
+ size_t len = 0U;
+
+ // no hash for 0 values
+ if (!value) {
+ flags &= ~FLAGS_HASH;
+ }
+
+ // write if precision != 0 and value is != 0
+ if (!(flags & FLAGS_PRECISION) || value) {
+ do {
+ const char digit = (char)(value % base);
+ buf[ len++ ] =
+ digit < 10 ? '0' + digit
+ : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
+ value /= base;
+ } while (value && (len < PRINTF_NTOA_BUFFER_SIZE));
+ }
+
+ return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative,
+ (unsigned int)base, prec, width, flags);
}
-
// internal itoa for 'long long' type
#if defined(PRINTF_SUPPORT_LONG_LONG)
-static size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long long value, bool negative, unsigned long long base, unsigned int prec, unsigned int width, unsigned int flags)
-{
- char buf[PRINTF_NTOA_BUFFER_SIZE];
- size_t len = 0U;
-
- // no hash for 0 values
- if (!value) {
- flags &= ~FLAGS_HASH;
- }
-
- // write if precision != 0 and value is != 0
- if (!(flags & FLAGS_PRECISION) || value) {
- do {
- const char digit = (char)(value % base);
- buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
- value /= base;
- } while (value && (len < PRINTF_NTOA_BUFFER_SIZE));
- }
-
- return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags);
+static size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx,
+ size_t maxlen, unsigned long long value,
+ bool negative, unsigned long long base,
+ unsigned int prec, unsigned int width,
+ unsigned int flags) {
+ char buf[ PRINTF_NTOA_BUFFER_SIZE ];
+ size_t len = 0U;
+
+ // no hash for 0 values
+ if (!value) {
+ flags &= ~FLAGS_HASH;
+ }
+
+ // write if precision != 0 and value is != 0
+ if (!(flags & FLAGS_PRECISION) || value) {
+ do {
+ const char digit = (char)(value % base);
+ buf[ len++ ] =
+ digit < 10 ? '0' + digit
+ : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
+ value /= base;
+ } while (value && (len < PRINTF_NTOA_BUFFER_SIZE));
+ }
+
+ return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative,
+ (unsigned int)base, prec, width, flags);
}
-#endif // PRINTF_SUPPORT_LONG_LONG
-
+#endif // PRINTF_SUPPORT_LONG_LONG
#if defined(PRINTF_SUPPORT_FLOAT)
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
-// forward declaration so that _ftoa can switch to exp notation for values > PRINTF_MAX_FLOAT
-static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags);
+// forward declaration so that _ftoa can switch to exp notation for values >
+// PRINTF_MAX_FLOAT
+static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen,
+ double value, unsigned int prec, unsigned int width,
+ unsigned int flags);
#endif
-
// internal ftoa for fixed decimal floating point
-static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags)
-{
- char buf[PRINTF_FTOA_BUFFER_SIZE];
- size_t len = 0U;
- double diff = 0.0;
-
- // powers of 10
- static const double pow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };
-
- // test for special values
- if (value != value)
- return _out_rev(out, buffer, idx, maxlen, "nan", 3, width, flags);
- if (value < -DBL_MAX)
- return _out_rev(out, buffer, idx, maxlen, "fni-", 4, width, flags);
- if (value > DBL_MAX)
- return _out_rev(out, buffer, idx, maxlen, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4U : 3U, width, flags);
-
- // test for very large values
- // standard printf behavior is to print EVERY whole number digit -- which could be 100s of characters overflowing your buffers == bad
- if ((value > PRINTF_MAX_FLOAT) || (value < -PRINTF_MAX_FLOAT)) {
+static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen,
+ double value, unsigned int prec, unsigned int width,
+ unsigned int flags) {
+ char buf[ PRINTF_FTOA_BUFFER_SIZE ];
+ size_t len = 0U;
+ double diff = 0.0;
+
+ // powers of 10
+ static const double pow10[] = {1, 10, 100, 1000,
+ 10000, 100000, 1000000, 10000000,
+ 100000000, 1000000000};
+
+ // test for special values
+ if (value != value)
+ return _out_rev(out, buffer, idx, maxlen, "nan", 3, width, flags);
+ if (value < -DBL_MAX)
+ return _out_rev(out, buffer, idx, maxlen, "fni-", 4, width, flags);
+ if (value > DBL_MAX)
+ return _out_rev(out, buffer, idx, maxlen,
+ (flags & FLAGS_PLUS) ? "fni+" : "fni",
+ (flags & FLAGS_PLUS) ? 4U : 3U, width, flags);
+
+ // test for very large values
+ // standard printf behavior is to print EVERY whole number digit -- which
+ // could be 100s of characters overflowing your buffers == bad
+ if ((value > PRINTF_MAX_FLOAT) || (value < -PRINTF_MAX_FLOAT)) {
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
- return _etoa(out, buffer, idx, maxlen, value, prec, width, flags);
+ return _etoa(out, buffer, idx, maxlen, value, prec, width, flags);
#else
- return 0U;
+ return 0U;
#endif
- }
-
- // test for negative
- bool negative = false;
- if (value < 0) {
- negative = true;
- value = 0 - value;
- }
-
- // set default precision, if not set explicitly
- if (!(flags & FLAGS_PRECISION)) {
- prec = PRINTF_DEFAULT_FLOAT_PRECISION;
- }
- // limit precision to 9, cause a prec >= 10 can lead to overflow errors
- while ((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) {
- buf[len++] = '0';
- prec--;
- }
-
- int whole = (int)value;
- double tmp = (value - whole) * pow10[prec];
- unsigned long frac = (unsigned long)tmp;
- diff = tmp - frac;
-
- if (diff > 0.5) {
- ++frac;
- // handle rollover, e.g. case 0.99 with prec 1 is 1.0
- if (frac >= pow10[prec]) {
- frac = 0;
- ++whole;
- }
- }
- else if (diff < 0.5) {
- }
- else if ((frac == 0U) || (frac & 1U)) {
- // if halfway, round up if odd OR if last digit is 0
- ++frac;
- }
-
- if (prec == 0U) {
- diff = value - (double)whole;
- if ((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) {
- // exactly 0.5 and ODD, then round up
- // 1.5 -> 2, but 2.5 -> 2
- ++whole;
- }
- }
- else {
- unsigned int count = prec;
- // now do fractional part, as an unsigned number
- while (len < PRINTF_FTOA_BUFFER_SIZE) {
- --count;
- buf[len++] = (char)(48U + (frac % 10U));
- if (!(frac /= 10U)) {
- break;
- }
- }
- // add extra 0s
- while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) {
- buf[len++] = '0';
- }
- if (len < PRINTF_FTOA_BUFFER_SIZE) {
- // add decimal
- buf[len++] = '.';
}
- }
- // do whole part, number is reversed
- while (len < PRINTF_FTOA_BUFFER_SIZE) {
- buf[len++] = (char)(48 + (whole % 10));
- if (!(whole /= 10)) {
- break;
+ // test for negative
+ bool negative = false;
+ if (value < 0) {
+ negative = true;
+ value = 0 - value;
}
- }
- // pad leading zeros
- if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) {
- if (width && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
- width--;
+ // set default precision, if not set explicitly
+ if (!(flags & FLAGS_PRECISION)) {
+ prec = PRINTF_DEFAULT_FLOAT_PRECISION;
}
- while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) {
- buf[len++] = '0';
+ // limit precision to 9, cause a prec >= 10 can lead to overflow errors
+ while ((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) {
+ buf[ len++ ] = '0';
+ prec--;
}
- }
- if (len < PRINTF_FTOA_BUFFER_SIZE) {
- if (negative) {
- buf[len++] = '-';
+ int whole = (int)value;
+ double tmp = (value - whole) * pow10[ prec ];
+ unsigned long frac = (unsigned long)tmp;
+ diff = tmp - frac;
+
+ if (diff > 0.5) {
+ ++frac;
+ // handle rollover, e.g. case 0.99 with prec 1 is 1.0
+ if (frac >= pow10[ prec ]) {
+ frac = 0;
+ ++whole;
+ }
+ } else if (diff < 0.5) {
+ } else if ((frac == 0U) || (frac & 1U)) {
+ // if halfway, round up if odd OR if last digit is 0
+ ++frac;
+ }
+
+ if (prec == 0U) {
+ diff = value - (double)whole;
+ if ((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) {
+ // exactly 0.5 and ODD, then round up
+ // 1.5 -> 2, but 2.5 -> 2
+ ++whole;
+ }
+ } else {
+ unsigned int count = prec;
+ // now do fractional part, as an unsigned number
+ while (len < PRINTF_FTOA_BUFFER_SIZE) {
+ --count;
+ buf[ len++ ] = (char)(48U + (frac % 10U));
+ if (!(frac /= 10U)) {
+ break;
+ }
+ }
+ // add extra 0s
+ while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) {
+ buf[ len++ ] = '0';
+ }
+ if (len < PRINTF_FTOA_BUFFER_SIZE) {
+ // add decimal
+ buf[ len++ ] = '.';
+ }
}
- else if (flags & FLAGS_PLUS) {
- buf[len++] = '+'; // ignore the space if the '+' exists
+
+ // do whole part, number is reversed
+ while (len < PRINTF_FTOA_BUFFER_SIZE) {
+ buf[ len++ ] = (char)(48 + (whole % 10));
+ if (!(whole /= 10)) {
+ break;
+ }
}
- else if (flags & FLAGS_SPACE) {
- buf[len++] = ' ';
+
+ // pad leading zeros
+ if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) {
+ if (width && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
+ width--;
+ }
+ while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) {
+ buf[ len++ ] = '0';
+ }
}
- }
- return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);
-}
+ if (len < PRINTF_FTOA_BUFFER_SIZE) {
+ if (negative) {
+ buf[ len++ ] = '-';
+ } else if (flags & FLAGS_PLUS) {
+ buf[ len++ ] = '+'; // ignore the space if the '+' exists
+ } else if (flags & FLAGS_SPACE) {
+ buf[ len++ ] = ' ';
+ }
+ }
+ return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);
+}
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
-// internal ftoa variant for exponential floating-point type, contributed by Martijn Jasperse
-static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags)
-{
- // check for NaN and special values
- if ((value != value) || (value > DBL_MAX) || (value < -DBL_MAX)) {
- return _ftoa(out, buffer, idx, maxlen, value, prec, width, flags);
- }
-
- // determine the sign
- const bool negative = value < 0;
- if (negative) {
- value = -value;
- }
-
- // default precision
- if (!(flags & FLAGS_PRECISION)) {
- prec = PRINTF_DEFAULT_FLOAT_PRECISION;
- }
-
- // determine the decimal exponent
- // based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c)
- union {
- uint64_t U;
- double F;
- } conv;
-
- conv.F = value;
- int exp2 = (int)((conv.U >> 52U) & 0x07FFU) - 1023; // effectively log2
- conv.U = (conv.U & ((1ULL << 52U) - 1U)) | (1023ULL << 52U); // drop the exponent so conv.F is now in [1,2)
- // now approximate log10 from the log2 integer part and an expansion of ln around 1.5
- int expval = (int)(0.1760912590558 + exp2 * 0.301029995663981 + (conv.F - 1.5) * 0.289529654602168);
- // now we want to compute 10^expval but we want to be sure it won't overflow
- exp2 = (int)(expval * 3.321928094887362 + 0.5);
- const double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453;
- const double z2 = z * z;
- conv.U = (uint64_t)(exp2 + 1023) << 52U;
- // compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex
- conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14)))));
- // correct for rounding errors
- if (value < conv.F) {
- expval--;
- conv.F /= 10;
- }
-
- // the exponent format is "%+03d" and largest value is "307", so set aside 4-5 characters
- unsigned int minwidth = ((expval < 100) && (expval > -100)) ? 4U : 5U;
-
- // in "%g" mode, "prec" is the number of *significant figures* not decimals
- if (flags & FLAGS_ADAPT_EXP) {
- // do we want to fall-back to "%f" mode?
- if ((value >= 1e-4) && (value < 1e6)) {
- if ((int)prec > expval) {
- prec = (unsigned)((int)prec - expval - 1);
- }
- else {
- prec = 0;
- }
- flags |= FLAGS_PRECISION; // make sure _ftoa respects precision
- // no characters in exponent
- minwidth = 0U;
- expval = 0;
- }
- else {
- // we use one sigfig for the whole part
- if ((prec > 0) && (flags & FLAGS_PRECISION)) {
- --prec;
- }
- }
- }
-
- // will everything fit?
- unsigned int fwidth = width;
- if (width > minwidth) {
- // we didn't fall-back so subtract the characters required for the exponent
- fwidth -= minwidth;
- } else {
- // not enough characters, so go back to default sizing
- fwidth = 0U;
- }
- if ((flags & FLAGS_LEFT) && minwidth) {
- // if we're padding on the right, DON'T pad the floating part
- fwidth = 0U;
- }
-
- // rescale the float value
- if (expval) {
- value /= conv.F;
- }
-
- // output the floating part
- const size_t start_idx = idx;
- idx = _ftoa(out, buffer, idx, maxlen, negative ? -value : value, prec, fwidth, flags & ~FLAGS_ADAPT_EXP);
-
- // output the exponent part
- if (minwidth) {
- // output the exponential symbol
- out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen);
- // output the exponent value
- idx = _ntoa_long(out, buffer, idx, maxlen, (expval < 0) ? -expval : expval, expval < 0, 10, 0, minwidth-1, FLAGS_ZEROPAD | FLAGS_PLUS);
- // might need to right-pad spaces
- if (flags & FLAGS_LEFT) {
- while (idx - start_idx < width) out(' ', buffer, idx++, maxlen);
+// internal ftoa variant for exponential floating-point type, contributed by
+// Martijn Jasperse
+static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen,
+ double value, unsigned int prec, unsigned int width,
+ unsigned int flags) {
+ // check for NaN and special values
+ if ((value != value) || (value > DBL_MAX) || (value < -DBL_MAX)) {
+ return _ftoa(out, buffer, idx, maxlen, value, prec, width, flags);
+ }
+
+ // determine the sign
+ const bool negative = value < 0;
+ if (negative) {
+ value = -value;
+ }
+
+ // default precision
+ if (!(flags & FLAGS_PRECISION)) {
+ prec = PRINTF_DEFAULT_FLOAT_PRECISION;
+ }
+
+ // determine the decimal exponent
+ // based on the algorithm by David Gay
+ // (https://www.ampl.com/netlib/fp/dtoa.c)
+ union {
+ uint64_t U;
+ double F;
+ } conv;
+
+ conv.F = value;
+ int exp2 = (int)((conv.U >> 52U) & 0x07FFU) - 1023; // effectively log2
+ conv.U = (conv.U & ((1ULL << 52U) - 1U)) |
+ (1023ULL << 52U); // drop the exponent so conv.F is now in [1,2)
+ // now approximate log10 from the log2 integer part and an expansion of ln
+ // around 1.5
+ int expval = (int)(0.1760912590558 + exp2 * 0.301029995663981 +
+ (conv.F - 1.5) * 0.289529654602168);
+ // now we want to compute 10^expval but we want to be sure it won't overflow
+ exp2 = (int)(expval * 3.321928094887362 + 0.5);
+ const double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453;
+ const double z2 = z * z;
+ conv.U = (uint64_t)(exp2 + 1023) << 52U;
+ // compute exp(z) using continued fractions, see
+ // https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex
+ conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14)))));
+ // correct for rounding errors
+ if (value < conv.F) {
+ expval--;
+ conv.F /= 10;
+ }
+
+ // the exponent format is "%+03d" and largest value is "307", so set aside
+ // 4-5 characters
+ unsigned int minwidth = ((expval < 100) && (expval > -100)) ? 4U : 5U;
+
+ // in "%g" mode, "prec" is the number of *significant figures* not decimals
+ if (flags & FLAGS_ADAPT_EXP) {
+ // do we want to fall-back to "%f" mode?
+ if ((value >= 1e-4) && (value < 1e6)) {
+ if ((int)prec > expval) {
+ prec = (unsigned)((int)prec - expval - 1);
+ } else {
+ prec = 0;
+ }
+ flags |= FLAGS_PRECISION; // make sure _ftoa respects precision
+ // no characters in exponent
+ minwidth = 0U;
+ expval = 0;
+ } else {
+ // we use one sigfig for the whole part
+ if ((prec > 0) && (flags & FLAGS_PRECISION)) {
+ --prec;
+ }
+ }
}
- }
- return idx;
-}
-#endif // PRINTF_SUPPORT_EXPONENTIAL
-#endif // PRINTF_SUPPORT_FLOAT
+ // will everything fit?
+ unsigned int fwidth = width;
+ if (width > minwidth) {
+ // we didn't fall-back so subtract the characters required for the
+ // exponent
+ fwidth -= minwidth;
+ } else {
+ // not enough characters, so go back to default sizing
+ fwidth = 0U;
+ }
+ if ((flags & FLAGS_LEFT) && minwidth) {
+ // if we're padding on the right, DON'T pad the floating part
+ fwidth = 0U;
+ }
+
+ // rescale the float value
+ if (expval) {
+ value /= conv.F;
+ }
+
+ // output the floating part
+ const size_t start_idx = idx;
+ idx = _ftoa(out, buffer, idx, maxlen, negative ? -value : value, prec,
+ fwidth, flags & ~FLAGS_ADAPT_EXP);
+
+ // output the exponent part
+ if (minwidth) {
+ // output the exponential symbol
+ out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen);
+ // output the exponent value
+ idx = _ntoa_long(out, buffer, idx, maxlen,
+ (expval < 0) ? -expval : expval, expval < 0, 10, 0,
+ minwidth - 1, FLAGS_ZEROPAD | FLAGS_PLUS);
+ // might need to right-pad spaces
+ if (flags & FLAGS_LEFT) {
+ while (idx - start_idx < width) out(' ', buffer, idx++, maxlen);
+ }
+ }
+ return idx;
+}
+#endif // PRINTF_SUPPORT_EXPONENTIAL
+#endif // PRINTF_SUPPORT_FLOAT
// internal vsnprintf
-static int _hedera_vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const char* format, va_list va)
-{
- unsigned int flags, width, precision, n;
- size_t idx = 0U;
-
- if (!buffer) {
- // use null output function
- out = _out_null;
- }
-
- while (*format)
- {
- // format specifier? %[flags][width][.precision][length]
- if (*format != '%') {
- // no
- out(*format, buffer, idx++, maxlen);
- format++;
- continue;
- }
- else {
- // yes, evaluate it
- format++;
- }
-
- // evaluate flags
- flags = 0U;
- do {
- switch (*format) {
- case '0': flags |= FLAGS_ZEROPAD; format++; n = 1U; break;
- case '-': flags |= FLAGS_LEFT; format++; n = 1U; break;
- case '+': flags |= FLAGS_PLUS; format++; n = 1U; break;
- case ' ': flags |= FLAGS_SPACE; format++; n = 1U; break;
- case '#': flags |= FLAGS_HASH; format++; n = 1U; break;
- default : n = 0U; break;
- }
- } while (n);
-
- // evaluate width field
- width = 0U;
- if (_is_digit(*format)) {
- width = _atoi(&format);
- }
- else if (*format == '*') {
- const int w = va_arg(va, int);
- if (w < 0) {
- flags |= FLAGS_LEFT; // reverse padding
- width = (unsigned int)-w;
- }
- else {
- width = (unsigned int)w;
- }
- format++;
- }
-
- // evaluate precision field
- precision = 0U;
- if (*format == '.') {
- flags |= FLAGS_PRECISION;
- format++;
- if (_is_digit(*format)) {
- precision = _atoi(&format);
- }
- else if (*format == '*') {
- const int prec = (int)va_arg(va, int);
- precision = prec > 0 ? (unsigned int)prec : 0U;
- format++;
- }
- }
-
- // evaluate length field
- switch (*format) {
- case 'l' :
- flags |= FLAGS_LONG;
- format++;
- if (*format == 'l') {
- flags |= FLAGS_LONG_LONG;
- format++;
- }
- break;
- case 'h' :
- flags |= FLAGS_SHORT;
- format++;
- if (*format == 'h') {
- flags |= FLAGS_CHAR;
- format++;
- }
- break;
-#if defined(PRINTF_SUPPORT_PTRDIFF_T)
- case 't' :
- flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
- format++;
- break;
-#endif
- case 'j' :
- flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
- format++;
- break;
- case 'z' :
- flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
- format++;
- break;
- default :
- break;
- }
-
- // evaluate specifier
- switch (*format) {
- case 'd' :
- case 'i' :
- case 'u' :
- case 'x' :
- case 'X' :
- case 'o' :
- case 'b' : {
- // set the base
- unsigned int base;
- if (*format == 'x' || *format == 'X') {
- base = 16U;
- }
- else if (*format == 'o') {
- base = 8U;
- }
- else if (*format == 'b') {
- base = 2U;
- }
- else {
- base = 10U;
- flags &= ~FLAGS_HASH; // no hash for dec format
+static int _hedera_vsnprintf(out_fct_type out, char* buffer,
+ const size_t maxlen, const char* format,
+ va_list va) {
+ unsigned int flags, width, precision, n;
+ size_t idx = 0U;
+
+ if (!buffer) {
+ // use null output function
+ out = _out_null;
+ }
+
+ while (*format) {
+ // format specifier? %[flags][width][.precision][length]
+ if (*format != '%') {
+ // no
+ out(*format, buffer, idx++, maxlen);
+ format++;
+ continue;
+ } else {
+ // yes, evaluate it
+ format++;
}
- // uppercase
- if (*format == 'X') {
- flags |= FLAGS_UPPERCASE;
+
+ // evaluate flags
+ flags = 0U;
+ do {
+ switch (*format) {
+ case '0':
+ flags |= FLAGS_ZEROPAD;
+ format++;
+ n = 1U;
+ break;
+ case '-':
+ flags |= FLAGS_LEFT;
+ format++;
+ n = 1U;
+ break;
+ case '+':
+ flags |= FLAGS_PLUS;
+ format++;
+ n = 1U;
+ break;
+ case ' ':
+ flags |= FLAGS_SPACE;
+ format++;
+ n = 1U;
+ break;
+ case '#':
+ flags |= FLAGS_HASH;
+ format++;
+ n = 1U;
+ break;
+ default:
+ n = 0U;
+ break;
+ }
+ } while (n);
+
+ // evaluate width field
+ width = 0U;
+ if (_is_digit(*format)) {
+ width = _atoi(&format);
+ } else if (*format == '*') {
+ const int w = va_arg(va, int);
+ if (w < 0) {
+ flags |= FLAGS_LEFT; // reverse padding
+ width = (unsigned int)-w;
+ } else {
+ width = (unsigned int)w;
+ }
+ format++;
}
- // no plus or space flag for u, x, X, o, b
- if ((*format != 'i') && (*format != 'd')) {
- flags &= ~(FLAGS_PLUS | FLAGS_SPACE);
+ // evaluate precision field
+ precision = 0U;
+ if (*format == '.') {
+ flags |= FLAGS_PRECISION;
+ format++;
+ if (_is_digit(*format)) {
+ precision = _atoi(&format);
+ } else if (*format == '*') {
+ const int prec = (int)va_arg(va, int);
+ precision = prec > 0 ? (unsigned int)prec : 0U;
+ format++;
+ }
}
- // ignore '0' flag when precision is given
- if (flags & FLAGS_PRECISION) {
- flags &= ~FLAGS_ZEROPAD;
+ // evaluate length field
+ switch (*format) {
+ case 'l':
+ flags |= FLAGS_LONG;
+ format++;
+ if (*format == 'l') {
+ flags |= FLAGS_LONG_LONG;
+ format++;
+ }
+ break;
+ case 'h':
+ flags |= FLAGS_SHORT;
+ format++;
+ if (*format == 'h') {
+ flags |= FLAGS_CHAR;
+ format++;
+ }
+ break;
+#if defined(PRINTF_SUPPORT_PTRDIFF_T)
+ case 't':
+ flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG
+ : FLAGS_LONG_LONG);
+ format++;
+ break;
+#endif
+ case 'j':
+ flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG
+ : FLAGS_LONG_LONG);
+ format++;
+ break;
+ case 'z':
+ flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG
+ : FLAGS_LONG_LONG);
+ format++;
+ break;
+ default:
+ break;
}
- // convert the integer
- if ((*format == 'i') || (*format == 'd')) {
- // signed
- if (flags & FLAGS_LONG_LONG) {
+ // evaluate specifier
+ switch (*format) {
+ case 'd':
+ case 'i':
+ case 'u':
+ case 'x':
+ case 'X':
+ case 'o':
+ case 'b': {
+ // set the base
+ unsigned int base;
+ if (*format == 'x' || *format == 'X') {
+ base = 16U;
+ } else if (*format == 'o') {
+ base = 8U;
+ } else if (*format == 'b') {
+ base = 2U;
+ } else {
+ base = 10U;
+ flags &= ~FLAGS_HASH; // no hash for dec format
+ }
+ // uppercase
+ if (*format == 'X') {
+ flags |= FLAGS_UPPERCASE;
+ }
+
+ // no plus or space flag for u, x, X, o, b
+ if ((*format != 'i') && (*format != 'd')) {
+ flags &= ~(FLAGS_PLUS | FLAGS_SPACE);
+ }
+
+ // ignore '0' flag when precision is given
+ if (flags & FLAGS_PRECISION) {
+ flags &= ~FLAGS_ZEROPAD;
+ }
+
+ // convert the integer
+ if ((*format == 'i') || (*format == 'd')) {
+ // signed
+ if (flags & FLAGS_LONG_LONG) {
#if defined(PRINTF_SUPPORT_LONG_LONG)
- const long long value = va_arg(va, long long);
- idx = _ntoa_long_long(out, buffer, idx, maxlen, (unsigned long long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
+ const long long value = va_arg(va, long long);
+ idx = _ntoa_long_long(
+ out, buffer, idx, maxlen,
+ (unsigned long long)(value > 0 ? value : 0 - value),
+ value < 0, base, precision, width, flags);
#endif
- }
- else if (flags & FLAGS_LONG) {
- const long value = va_arg(va, long);
- idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
- }
- else {
- const int value = (flags & FLAGS_CHAR) ? (char)va_arg(va, int) : (flags & FLAGS_SHORT) ? (short int)va_arg(va, int) : va_arg(va, int);
- idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned int)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
- }
- }
- else {
- // unsigned
- if (flags & FLAGS_LONG_LONG) {
+ } else if (flags & FLAGS_LONG) {
+ const long value = va_arg(va, long);
+ idx = _ntoa_long(
+ out, buffer, idx, maxlen,
+ (unsigned long)(value > 0 ? value : 0 - value),
+ value < 0, base, precision, width, flags);
+ } else {
+ const int value =
+ (flags & FLAGS_CHAR) ? (char)va_arg(va, int)
+ : (flags & FLAGS_SHORT) ? (short int)va_arg(va, int)
+ : va_arg(va, int);
+ idx = _ntoa_long(
+ out, buffer, idx, maxlen,
+ (unsigned int)(value > 0 ? value : 0 - value),
+ value < 0, base, precision, width, flags);
+ }
+ } else {
+ // unsigned
+ if (flags & FLAGS_LONG_LONG) {
#if defined(PRINTF_SUPPORT_LONG_LONG)
- idx = _ntoa_long_long(out, buffer, idx, maxlen, va_arg(va, unsigned long long), false, base, precision, width, flags);
+ idx = _ntoa_long_long(out, buffer, idx, maxlen,
+ va_arg(va, unsigned long long),
+ false, base, precision, width,
+ flags);
#endif
- }
- else if (flags & FLAGS_LONG) {
- idx = _ntoa_long(out, buffer, idx, maxlen, va_arg(va, unsigned long), false, base, precision, width, flags);
- }
- else {
- const unsigned int value = (flags & FLAGS_CHAR) ? (unsigned char)va_arg(va, unsigned int) : (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(va, unsigned int) : va_arg(va, unsigned int);
- idx = _ntoa_long(out, buffer, idx, maxlen, value, false, base, precision, width, flags);
- }
- }
- format++;
- break;
- }
+ } else if (flags & FLAGS_LONG) {
+ idx = _ntoa_long(out, buffer, idx, maxlen,
+ va_arg(va, unsigned long), false, base,
+ precision, width, flags);
+ } else {
+ const unsigned int value =
+ (flags & FLAGS_CHAR)
+ ? (unsigned char)va_arg(va, unsigned int)
+ : (flags & FLAGS_SHORT)
+ ? (unsigned short int)va_arg(va, unsigned int)
+ : va_arg(va, unsigned int);
+ idx = _ntoa_long(out, buffer, idx, maxlen, value, false,
+ base, precision, width, flags);
+ }
+ }
+ format++;
+ break;
+ }
#if defined(PRINTF_SUPPORT_FLOAT)
- case 'f' :
- case 'F' :
- if (*format == 'F') flags |= FLAGS_UPPERCASE;
- idx = _ftoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags);
- format++;
- break;
+ case 'f':
+ case 'F':
+ if (*format == 'F') flags |= FLAGS_UPPERCASE;
+ idx = _ftoa(out, buffer, idx, maxlen, va_arg(va, double),
+ precision, width, flags);
+ format++;
+ break;
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
- case 'e':
- case 'E':
- case 'g':
- case 'G':
- if ((*format == 'g')||(*format == 'G')) flags |= FLAGS_ADAPT_EXP;
- if ((*format == 'E')||(*format == 'G')) flags |= FLAGS_UPPERCASE;
- idx = _etoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags);
- format++;
- break;
-#endif // PRINTF_SUPPORT_EXPONENTIAL
-#endif // PRINTF_SUPPORT_FLOAT
- case 'c' : {
- unsigned int l = 1U;
- // pre padding
- if (!(flags & FLAGS_LEFT)) {
- while (l++ < width) {
- out(' ', buffer, idx++, maxlen);
- }
- }
- // char output
- out((char)va_arg(va, int), buffer, idx++, maxlen);
- // post padding
- if (flags & FLAGS_LEFT) {
- while (l++ < width) {
- out(' ', buffer, idx++, maxlen);
- }
- }
- format++;
- break;
- }
-
- case 's' : {
- const char* p = va_arg(va, char*);
- unsigned int l = _strnlen_s(p, precision ? precision : (size_t)-1);
- // pre padding
- if (flags & FLAGS_PRECISION) {
- l = (l < precision ? l : precision);
- }
- if (!(flags & FLAGS_LEFT)) {
- while (l++ < width) {
- out(' ', buffer, idx++, maxlen);
- }
- }
- // string output
- while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--)) {
- out(*(p++), buffer, idx++, maxlen);
- }
- // post padding
- if (flags & FLAGS_LEFT) {
- while (l++ < width) {
- out(' ', buffer, idx++, maxlen);
- }
- }
- format++;
- break;
- }
-
- case 'p' : {
- width = sizeof(void*) * 2U;
- flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE;
+ case 'e':
+ case 'E':
+ case 'g':
+ case 'G':
+ if ((*format == 'g') || (*format == 'G'))
+ flags |= FLAGS_ADAPT_EXP;
+ if ((*format == 'E') || (*format == 'G'))
+ flags |= FLAGS_UPPERCASE;
+ idx = _etoa(out, buffer, idx, maxlen, va_arg(va, double),
+ precision, width, flags);
+ format++;
+ break;
+#endif // PRINTF_SUPPORT_EXPONENTIAL
+#endif // PRINTF_SUPPORT_FLOAT
+ case 'c': {
+ unsigned int l = 1U;
+ // pre padding
+ if (!(flags & FLAGS_LEFT)) {
+ while (l++ < width) {
+ out(' ', buffer, idx++, maxlen);
+ }
+ }
+ // char output
+ out((char)va_arg(va, int), buffer, idx++, maxlen);
+ // post padding
+ if (flags & FLAGS_LEFT) {
+ while (l++ < width) {
+ out(' ', buffer, idx++, maxlen);
+ }
+ }
+ format++;
+ break;
+ }
+
+ case 's': {
+ const char* p = va_arg(va, char*);
+ unsigned int l =
+ _strnlen_s(p, precision ? precision : (size_t)-1);
+ // pre padding
+ if (flags & FLAGS_PRECISION) {
+ l = (l < precision ? l : precision);
+ }
+ if (!(flags & FLAGS_LEFT)) {
+ while (l++ < width) {
+ out(' ', buffer, idx++, maxlen);
+ }
+ }
+ // string output
+ while ((*p != 0) &&
+ (!(flags & FLAGS_PRECISION) || precision--)) {
+ out(*(p++), buffer, idx++, maxlen);
+ }
+ // post padding
+ if (flags & FLAGS_LEFT) {
+ while (l++ < width) {
+ out(' ', buffer, idx++, maxlen);
+ }
+ }
+ format++;
+ break;
+ }
+
+ case 'p': {
+ width = sizeof(void*) * 2U;
+ flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE;
#if defined(PRINTF_SUPPORT_LONG_LONG)
- const bool is_ll = sizeof(uintptr_t) == sizeof(long long);
- if (is_ll) {
- idx = _ntoa_long_long(out, buffer, idx, maxlen, (uintptr_t)va_arg(va, void*), false, 16U, precision, width, flags);
- }
- else {
+ const bool is_ll = sizeof(uintptr_t) == sizeof(long long);
+ if (is_ll) {
+ idx = _ntoa_long_long(out, buffer, idx, maxlen,
+ (uintptr_t)va_arg(va, void*), false,
+ 16U, precision, width, flags);
+ } else {
#endif
- idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)((uintptr_t)va_arg(va, void*)), false, 16U, precision, width, flags);
+ idx = _ntoa_long(
+ out, buffer, idx, maxlen,
+ (unsigned long)((uintptr_t)va_arg(va, void*)), false,
+ 16U, precision, width, flags);
#if defined(PRINTF_SUPPORT_LONG_LONG)
- }
+ }
#endif
- format++;
- break;
- }
-
- case '%' :
- out('%', buffer, idx++, maxlen);
- format++;
- break;
-
- default :
- out(*format, buffer, idx++, maxlen);
- format++;
- break;
+ format++;
+ break;
+ }
+
+ case '%':
+ out('%', buffer, idx++, maxlen);
+ format++;
+ break;
+
+ default:
+ out(*format, buffer, idx++, maxlen);
+ format++;
+ break;
+ }
}
- }
- // termination
- out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen);
+ // termination
+ out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen);
- // return written chars without terminating \0
- return (int)idx;
+ // return written chars without terminating \0
+ return (int)idx;
}
-
///////////////////////////////////////////////////////////////////////////////
-int hedera_printf_(const char* format, ...)
-{
- va_list va;
- va_start(va, format);
- char buffer[1];
- const int ret = _hedera_vsnprintf(_out_char, buffer, (size_t)-1, format, va);
- va_end(va);
- return ret;
+int hedera_printf_(const char* format, ...) {
+ va_list va;
+ va_start(va, format);
+ char buffer[ 1 ];
+ const int ret =
+ _hedera_vsnprintf(_out_char, buffer, (size_t)-1, format, va);
+ va_end(va);
+ return ret;
}
-
-int hedera_sprintf_(char* buffer, const char* format, ...)
-{
- va_list va;
- va_start(va, format);
- const int ret = _hedera_vsnprintf(_out_buffer, buffer, (size_t)-1, format, va);
- va_end(va);
- return ret;
+int hedera_sprintf_(char* buffer, const char* format, ...) {
+ va_list va;
+ va_start(va, format);
+ const int ret =
+ _hedera_vsnprintf(_out_buffer, buffer, (size_t)-1, format, va);
+ va_end(va);
+ return ret;
}
-
-int hedera_snprintf_(char* buffer, size_t count, const char* format, ...)
-{
- va_list va;
- va_start(va, format);
- const int ret = _hedera_vsnprintf(_out_buffer, buffer, count, format, va);
- va_end(va);
- return ret;
+int hedera_snprintf_(char* buffer, size_t count, const char* format, ...) {
+ va_list va;
+ va_start(va, format);
+ const int ret = _hedera_vsnprintf(_out_buffer, buffer, count, format, va);
+ va_end(va);
+ return ret;
}
-
-int hedera_vprintf_(const char* format, va_list va)
-{
- char buffer[1];
- return _hedera_vsnprintf(_out_char, buffer, (size_t)-1, format, va);
+int hedera_vprintf_(const char* format, va_list va) {
+ char buffer[ 1 ];
+ return _hedera_vsnprintf(_out_char, buffer, (size_t)-1, format, va);
}
-
-int hedera_vsnprintf_(char* buffer, size_t count, const char* format, va_list va)
-{
- return _hedera_vsnprintf(_out_buffer, buffer, count, format, va);
+int hedera_vsnprintf_(char* buffer, size_t count, const char* format,
+ va_list va) {
+ return _hedera_vsnprintf(_out_buffer, buffer, count, format, va);
}
-
-int hedera_fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...)
-{
- va_list va;
- va_start(va, format);
- const out_fct_wrap_type out_fct_wrap = { out, arg };
- const int ret = _hedera_vsnprintf(_out_fct, (char*)(uintptr_t)&out_fct_wrap, (size_t)-1, format, va);
- va_end(va);
- return ret;
+int hedera_fctprintf(void (*out)(char character, void* arg), void* arg,
+ const char* format, ...) {
+ va_list va;
+ va_start(va, format);
+ const out_fct_wrap_type out_fct_wrap = {out, arg};
+ const int ret = _hedera_vsnprintf(_out_fct, (char*)(uintptr_t)&out_fct_wrap,
+ (size_t)-1, format, va);
+ va_end(va);
+ return ret;
}
diff --git a/src/printf.h b/src/printf.h
index b30c54d4..d68575b4 100644
--- a/src/printf.h
+++ b/src/printf.h
@@ -10,10 +10,10 @@
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
-//
+//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
-//
+//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -22,7 +22,8 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
-// \brief Tiny printf, sprintf and snprintf implementation, optimized for speed on
+// \brief Tiny printf, sprintf and snprintf implementation, optimized for speed
+// on
// embedded systems with a very limited resources.
// Use this instead of bloated standard/newlib printf.
// These routines are thread safe and reentrant.
@@ -35,83 +36,82 @@
#include
#include
-
#ifdef __cplusplus
extern "C" {
#endif
-
/**
- * Output a character to a custom device like UART, used by the printf() function
- * This function is declared here only. You have to write your custom implementation somewhere
- * \param character Character to output
+ * Output a character to a custom device like UART, used by the printf()
+ * function This function is declared here only. You have to write your custom
+ * implementation somewhere \param character Character to output
*/
void _putchar(char character);
-
/**
* Tiny printf implementation
* You have to implement _putchar if you use printf()
- * To avoid conflicts with the regular printf() API it is overridden by macro defines
- * and internal underscore-appended functions like printf_() are used
+ * To avoid conflicts with the regular printf() API it is overridden by macro
+ * defines and internal underscore-appended functions like printf_() are used
* \param format A string that specifies the format of the output
- * \return The number of characters that are written into the array, not counting the terminating null character
+ * \return The number of characters that are written into the array, not
+ * counting the terminating null character
*/
#define hedera_printf hedera_printf_
int hedera_printf_(const char* format, ...);
-
/**
* Tiny sprintf implementation
- * Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING (V)SNPRINTF INSTEAD!
- * \param buffer A pointer to the buffer where to store the formatted string. MUST be big enough to store the output!
- * \param format A string that specifies the format of the output
- * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character
+ * Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING
+ * (V)SNPRINTF INSTEAD! \param buffer A pointer to the buffer where to store the
+ * formatted string. MUST be big enough to store the output! \param format A
+ * string that specifies the format of the output \return The number of
+ * characters that are WRITTEN into the buffer, not counting the terminating
+ * null character
*/
#define hedera_sprintf hedera_sprintf_
int hedera_sprintf_(char* buffer, const char* format, ...);
-
/**
* Tiny snprintf/vsnprintf implementation
* \param buffer A pointer to the buffer where to store the formatted string
- * \param count The maximum number of characters to store in the buffer, including a terminating null character
- * \param format A string that specifies the format of the output
- * \param va A value identifying a variable arguments list
- * \return The number of characters that COULD have been written into the buffer, not counting the terminating
- * null character. A value equal or larger than count indicates truncation. Only when the returned value
- * is non-negative and less than count, the string has been completely written.
+ * \param count The maximum number of characters to store in the buffer,
+ * including a terminating null character \param format A string that specifies
+ * the format of the output \param va A value identifying a variable arguments
+ * list \return The number of characters that COULD have been written into the
+ * buffer, not counting the terminating null character. A value equal or larger
+ * than count indicates truncation. Only when the returned value is non-negative
+ * and less than count, the string has been completely written.
*/
-#define hedera_snprintf hedera_snprintf_
+#define hedera_snprintf hedera_snprintf_
#define hedera_vsnprintf hedera_vsnprintf_
-int hedera_snprintf_(char* buffer, size_t count, const char* format, ...);
-int hedera_vsnprintf_(char* buffer, size_t count, const char* format, va_list va);
-
+int hedera_snprintf_(char* buffer, size_t count, const char* format, ...);
+int hedera_vsnprintf_(char* buffer, size_t count, const char* format,
+ va_list va);
/**
* Tiny vprintf implementation
* \param format A string that specifies the format of the output
* \param va A value identifying a variable arguments list
- * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character
+ * \return The number of characters that are WRITTEN into the buffer, not
+ * counting the terminating null character
*/
#define hedera_vprintf hedera_vprintf_
int hedera_vprintf_(const char* format, va_list va);
-
/**
* printf with output function
- * You may use this as dynamic alternative to printf() with its fixed _putchar() output
- * \param out An output function which takes one character and an argument pointer
- * \param arg An argument pointer for user data passed to output function
- * \param format A string that specifies the format of the output
- * \return The number of characters that are sent to the output function, not counting the terminating null character
+ * You may use this as dynamic alternative to printf() with its fixed _putchar()
+ * output \param out An output function which takes one character and an
+ * argument pointer \param arg An argument pointer for user data passed to
+ * output function \param format A string that specifies the format of the
+ * output \return The number of characters that are sent to the output function,
+ * not counting the terminating null character
*/
-int hedera_fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...);
-
+int hedera_fctprintf(void (*out)(char character, void* arg), void* arg,
+ const char* format, ...);
#ifdef __cplusplus
}
#endif
-
-#endif // _PRINTF_H_
+#endif // _PRINTF_H_
diff --git a/src/sign_transaction.c b/src/sign_transaction.c
index 9b44501e..119b0514 100644
--- a/src/sign_transaction.c
+++ b/src/sign_transaction.c
@@ -1,69 +1,68 @@
-#include
-#include
-#include
-#include
-#include
-
-#include "printf.h"
-#include "globals.h"
-#include "debug.h"
-#include "errors.h"
-#include "handlers.h"
-#include "hedera.h"
-#include "hedera_format.h"
-#include "io.h"
-#include "TransactionBody.pb.h"
-#include "utils.h"
-#include "ui.h"
#include "sign_transaction.h"
#if defined(TARGET_NANOS)
-static struct sign_tx_context_t {
- // ui common
- uint32_t key_index;
- uint8_t transfer_to_index;
- uint8_t transfer_from_index;
-
- // Transaction Summary
- char summary_line_1[DISPLAY_SIZE + 1];
- char summary_line_2[DISPLAY_SIZE + 1];
- char title[DISPLAY_SIZE + 1];
-
- // Account ID: uint64_t.uint64_t.uint64_t
- // Most other entities are shorter
- char full[ACCOUNT_ID_SIZE + 1];
- char partial[DISPLAY_SIZE + 1];
-
- // Steps correspond to parts of the transaction proto
- // type is set based on proto
- enum TransactionStep step;
- enum TransactionType type;
-
- uint8_t display_index; // 1 -> Number Screens
- uint8_t display_count; // Number Screens
-
- // Parsed transaction
- HederaTransactionBody transaction;
-} ctx;
-
-// UI Definition for Nano S
+/*
+ * Supported Transactions:
+ *
+ * Verify:
+ * "Verify Account with Key #0?" (Summary) <--> "Account" (Senders) <--> Confirm
+<--> Deny
+ *
+ * Create:
+ * "Create Account with Key #0?" (Summary) <--> Operator <--> "Stake to"
+(Senders) <--> "Collect Rewards? Yes / No" (Recipients) <--> "Initial Balance"
+(Amount) <--> Fee <--> Memo <--> Confirm <--> Deny
+ *
+ * Update:
+ * "Update Account 0.0.0 with Key #0?" (Summary) <--> Operator <--> "Stake to"
+(Senders) <--> "Collect Rewards (Yes / No)" (Recipients) <--> "Updated Account"
+(Amount) <--> Fee <--> Memo <--> Confirm <--> Deny
+ *
+ * Transfer:
+ * "Transfer with Key #0?" (Summary) <--> Operator <--> Senders <--> Recipients
+<--> Amount <--> Fee <--> Memo <--> Confirm <--> Deny
+ *
+ * Associate:
+ * "Associate Token with Key #0?" (Summary) <--> Operator <--> "Token" (Senders)
+<--> "Updating" (Amount) <--> Fee <--> Memo <--> Confirm <--> Deny
+ *
+ * Dissociate:
+ * "Dissociate Token with Key #0?" (Summary) <--> Operator <--> "Token"
+(Senders) <--> "Updating" (Amount) <--> Fee <--> Memo <--> Confirm <--> Deny
+ *
+ * TokenMint:
+ * "Mint Token with Key #0?" (Summary) <--> Operator <--> "Token" (Senders) <-->
+Amount <--> Fee <--> Memo <--> Confirm <--> Deny
+ *
+ * TokenBurn:
+ * "Burn Token with Key #0?" (Summary) <--> Operator <--> "Token" (Senders) <-->
+Amount <--> Fee <--> Memo <--> Confirm <--> Deny
+ *
+ * I chose the steps for the originally supported CreateAccount and Transfer
+transactions, and the additional transactions have been added since then. Steps
+may be skipped or modified (as described above) from the original transfer flow.
+The implementation of the steps is in the 'intermediate' screen button handlers.
+These functions control iterating through the steps and control paging for
+entities that overflow display on a single screen. The nano X has a paging macro
+for this in its UX system. We don't have much RAM to work with, so I couldn't
+define entirely separate UI elements for each flow, which lead me to using the
+screens defined below as singletons.
+ */
+
// Step 1: Transaction Summary
static const bagl_element_t ui_tx_summary_step[] = {
- UI_BACKGROUND(),
- UI_ICON_RIGHT(RIGHT_ICON_ID, BAGL_GLYPH_ICON_RIGHT),
+ UI_BACKGROUND(), UI_ICON_RIGHT(RIGHT_ICON_ID, BAGL_GLYPH_ICON_RIGHT),
// () >>
// Line 1
// Line 2
UI_TEXT(LINE_1_ID, 0, 12, 128, ctx.summary_line_1),
- UI_TEXT(LINE_2_ID, 0, 26, 128, ctx.summary_line_2)
-};
+ UI_TEXT(LINE_2_ID, 0, 26, 128, ctx.summary_line_2)};
// Step 2 - 7: Operator, Senders, Recipients, Amount, Fee, Memo
static const bagl_element_t ui_tx_intermediate_step[] = {
- UI_BACKGROUND(),
- UI_ICON_LEFT(LEFT_ICON_ID, BAGL_GLYPH_ICON_LEFT),
+ UI_BACKGROUND(), UI_ICON_LEFT(LEFT_ICON_ID, BAGL_GLYPH_ICON_LEFT),
UI_ICON_RIGHT(RIGHT_ICON_ID, BAGL_GLYPH_ICON_RIGHT),
// << >>
@@ -71,13 +70,11 @@ static const bagl_element_t ui_tx_intermediate_step[] = {
//
UI_TEXT(LINE_1_ID, 0, 12, 128, ctx.title),
- UI_TEXT(LINE_2_ID, 0, 26, 128, ctx.partial)
-};
+ UI_TEXT(LINE_2_ID, 0, 26, 128, ctx.partial)};
// Step 8: Confirm
static const bagl_element_t ui_tx_confirm_step[] = {
- UI_BACKGROUND(),
- UI_ICON_LEFT(LEFT_ICON_ID, BAGL_GLYPH_ICON_LEFT),
+ UI_BACKGROUND(), UI_ICON_LEFT(LEFT_ICON_ID, BAGL_GLYPH_ICON_LEFT),
UI_ICON_RIGHT(RIGHT_ICON_ID, BAGL_GLYPH_ICON_RIGHT),
// << >>
@@ -85,36 +82,32 @@ static const bagl_element_t ui_tx_confirm_step[] = {
//
UI_TEXT(LINE_1_ID, 0, 12, 128, "Confirm"),
- UI_ICON(LINE_2_ID, 0, 24, 128, BAGL_GLYPH_ICON_CHECK)
-};
+ UI_ICON(LINE_2_ID, 0, 24, 128, BAGL_GLYPH_ICON_CHECK)};
// Step 9: Deny
static const bagl_element_t ui_tx_deny_step[] = {
- UI_BACKGROUND(),
- UI_ICON_LEFT(LEFT_ICON_ID, BAGL_GLYPH_ICON_LEFT),
+ UI_BACKGROUND(), UI_ICON_LEFT(LEFT_ICON_ID, BAGL_GLYPH_ICON_LEFT),
// << ()
// Deny
// X
UI_TEXT(LINE_1_ID, 0, 12, 128, "Deny"),
- UI_ICON(LINE_2_ID, 0, 24, 128, BAGL_GLYPH_ICON_CROSS)
-};
+ UI_ICON(LINE_2_ID, 0, 24, 128, BAGL_GLYPH_ICON_CROSS)};
-// Step 1: Transaction Summary
-unsigned int ui_tx_summary_step_button(
- unsigned int button_mask,
- unsigned int __attribute__ ((unused)) button_mask_counter
-) {
- switch(button_mask) {
+// Step 1: Transaction Summary --> Operator, Senders
+unsigned int ui_tx_summary_step_button(unsigned int button_mask,
+ unsigned int __attribute__((unused))
+ button_mask_counter) {
+ switch (button_mask) {
case BUTTON_EVT_RELEASED | BUTTON_RIGHT:
- if (ctx.type == Verify || ctx.type == Associate || ctx.type == TokenMint || ctx.type == TokenBurn) {
+ ctx.current_page = 1;
+
+ if (ctx.type == Verify) {
ctx.step = Senders;
- ctx.display_index = 1;
reformat_senders();
} else {
ctx.step = Operator;
- ctx.display_index = 1;
reformat_operator();
}
UX_DISPLAY(ui_tx_intermediate_step, NULL);
@@ -128,97 +121,102 @@ void handle_intermediate_left_press() {
// Navigate Left (scroll or return to previous step)
switch (ctx.step) {
case Operator: {
- if (first_screen()) { // Return to Summary
+ if (first_screen_of_step()) {
+ // All: Summary <-- Operator
+ ctx.current_page = 1;
ctx.step = Summary;
- ctx.display_index = 1;
UX_DISPLAY(ui_tx_summary_step, NULL);
- } else { // Scroll Left
- ctx.display_index--;
+ } else { // Scroll Left
+ ctx.current_page--;
reformat_operator();
- UX_REDISPLAY();
}
} break;
case Senders: {
- if (first_screen()) { // Return to Operator
- if (ctx.type == Verify || ctx.type == Associate || ctx.type == TokenMint || ctx.type == TokenBurn) {
+ if (first_screen_of_step()) { // Return to Operator
+ ctx.current_page = 1;
+
+ if (ctx.type == Verify) {
+ // Verify: Summary <-- Senders
ctx.step = Summary;
- ctx.display_index = 1;
UX_DISPLAY(ui_tx_summary_step, NULL);
} else {
+ // All (!Verify): Operator <-- Senders
ctx.step = Operator;
- ctx.display_index = 1;
reformat_operator();
}
- } else { // Scroll Left
- ctx.display_index--;
+ } else { // Scroll Left
+ ctx.current_page--;
reformat_senders();
}
- UX_REDISPLAY();
+
} break;
case Recipients: {
- if (first_screen()) { // Return to Senders
+ if (first_screen_of_step()) {
+ // All: Senders <-- Recipients
+ ctx.current_page = 1;
ctx.step = Senders;
- ctx.display_index = 1;
reformat_senders();
- } else { // Scroll Left
- ctx.display_index--;
+ } else { // Scroll Left
+ ctx.current_page--;
reformat_recipients();
}
- UX_REDISPLAY();
+
} break;
case Amount: {
- if (first_screen()) {
- if (ctx.type == Create) { // Return to Operator
- ctx.step = Operator;
- ctx.display_index = 1;
- reformat_operator();
- } else if (ctx.type == Transfer || ctx.type == TokenTransfer) { // Return to Recipients
- ctx.step = Recipients;
- ctx.display_index = 1;
- reformat_recipients();
- } else if (ctx.type == TokenMint || ctx.type == TokenBurn) { // Return to Senders
+ if (first_screen_of_step()) {
+ ctx.current_page = 1;
+
+ if (ctx.type == TokenMint || ctx.type == TokenBurn ||
+ ctx.type == Associate || ctx.type == Dissociate) {
+ // Mint, Burn, Associate, Dissociate: Senders <-- Amount
ctx.step = Senders;
- ctx.display_index = 1;
reformat_senders();
+ } else {
+ // All (!Mint, !Burn, !Associate, !Dissociate): Recipients
+ // <-- Amount
+ ctx.step = Recipients;
+ reformat_recipients();
}
- } else { // Scroll left
- ctx.display_index--;
+ } else { // Scroll left
+ ctx.current_page--;
reformat_amount();
}
- UX_REDISPLAY();
+
} break;
case Fee: {
- if (first_screen()) { // Return to Amount
+ if (first_screen_of_step()) {
+ // All: Amount <-- Fee
+ ctx.current_page = 1;
ctx.step = Amount;
- ctx.display_index = 1;
reformat_amount();
- } else { // Scroll left
- ctx.display_index--;
+ } else { // Scroll left
+ ctx.current_page--;
reformat_fee();
}
- UX_REDISPLAY();
+
} break;
case Memo: {
- if (first_screen()) { // Return to Fee
+ if (first_screen_of_step()) {
+ // All: Fee <-- Memo
+ ctx.current_page = 1;
ctx.step = Fee;
- ctx.display_index = 1;
reformat_fee();
- } else { // Scroll Left
- ctx.display_index--;
+ } else { // Scroll Left
+ ctx.current_page--;
reformat_memo();
}
- UX_REDISPLAY();
+
} break;
case Summary:
case Confirm:
case Deny:
- // ignore left button on Summary, Confirm, and Deny screens
+ // Should not occur, does not handle these steps
break;
}
}
@@ -227,114 +225,107 @@ void handle_intermediate_right_press() {
// Navigate Right (scroll or continue to next step)
switch (ctx.step) {
case Operator: {
- if (last_screen()) {
- if (ctx.type == Create) { // Continue to Amount
- ctx.step = Amount;
- ctx.display_index = 1;
- reformat_amount();
- } else { // Continue to Senders
- ctx.step = Senders;
- ctx.display_index = 1;
- reformat_senders();
- }
- } else { // Scroll Right
- ctx.display_index++;
+ if (last_screen_of_step()) {
+ // All: Operator --> Senders
+ ctx.step = Senders;
+ ctx.current_page = 1;
+ reformat_senders();
+ } else { // Scroll Right
+ ctx.current_page++;
reformat_operator();
}
- UX_REDISPLAY();
+
} break;
case Senders: {
- if (last_screen()) {
- if (ctx.type == Verify || ctx.type == Associate) { // Continue to Confirm
+ if (last_screen_of_step()) {
+ ctx.current_page = 1;
+
+ if (ctx.type == Verify) {
+ // Verify: Senders --> Confirm
ctx.step = Confirm;
UX_DISPLAY(ui_tx_confirm_step, NULL);
- } else if (ctx.type == TokenMint || ctx.type == TokenBurn) {
+ } else if (ctx.type == TokenMint || ctx.type == TokenBurn ||
+ ctx.type == Associate || ctx.type == Dissociate) {
+ // Mint, Burn: Senders --> Amount
ctx.step = Amount;
- ctx.display_index = 1;
reformat_amount();
- } else { // Continue to Recipients
+ } else {
+ // Create, Update, Transfer: Senders --> Recipients
ctx.step = Recipients;
- ctx.display_index = 1;
reformat_recipients();
}
- } else { // Scroll Right
- ctx.display_index++;
+ } else { // Scroll Right
+ ctx.current_page++;
reformat_senders();
}
- UX_REDISPLAY();
+
} break;
case Recipients: {
- if (last_screen()) { // Continue to Amount
+ if (last_screen_of_step()) {
+ // All (Create, Update, Transfer): Recipients --> Amount
ctx.step = Amount;
- ctx.display_index = 1;
+ ctx.current_page = 1;
reformat_amount();
- } else { // Scroll Right
- ctx.display_index++;
+ } else { // Scroll Right
+ ctx.current_page++;
reformat_recipients();
}
- UX_REDISPLAY();
+
} break;
case Amount: {
- if (last_screen()) {
- if (ctx.type == TokenMint || ctx.type == TokenBurn) {
- // Continue to Confirm
- ctx.step = Confirm;
- ctx.display_index = 1;
- UX_DISPLAY(ui_tx_confirm_step, NULL);
- } else {
- // Continue to Fee
- ctx.step = Fee;
- ctx.display_index = 1;
- reformat_fee();
- }
- } else { // Scroll Right
- ctx.display_index++;
+ if (last_screen_of_step()) {
+ // All: Amount --> Fee
+ ctx.step = Fee;
+ ctx.current_page = 1;
+ reformat_fee();
+ } else { // Scroll Right
+ ctx.current_page++;
reformat_amount();
}
- UX_REDISPLAY();
+
} break;
case Fee: {
- if (last_screen()) { // Continue to Memo
+ if (last_screen_of_step()) {
+ // All: Fee --> Memo
ctx.step = Memo;
- ctx.display_index = 1;
+ ctx.current_page = 1;
reformat_memo();
- } else { // Scroll Right
- ctx.display_index++;
+ } else { // Scroll Right
+ ctx.current_page++;
reformat_fee();
}
- UX_REDISPLAY();
+
} break;
case Memo: {
- if (last_screen()) { // Continue to Confirm
+ if (last_screen_of_step()) {
+ // All: Memo --> Confirm
ctx.step = Confirm;
- ctx.display_index = 1;
+ ctx.current_page = 1;
UX_DISPLAY(ui_tx_confirm_step, NULL);
- } else { // Scroll Right
- ctx.display_index++;
+ } else { // Scroll Right
+ ctx.current_page++;
reformat_memo();
- UX_REDISPLAY();
}
} break;
case Summary:
case Confirm:
case Deny:
- // ignore left button on Summary, Confirm, and Deny screens
+ // Should not occur, does not handle these steps
break;
}
}
// Step 2 - 7: Operator, Senders, Recipients, Amount, Fee, Memo
-unsigned int ui_tx_intermediate_step_button(
- unsigned int button_mask,
- unsigned int __attribute__ ((unused)) button_mask_counter
-) {
- switch(button_mask) {
+unsigned int ui_tx_intermediate_step_button(unsigned int button_mask,
+ unsigned int __attribute__((unused))
+ button_mask_counter) {
+ switch (button_mask) {
case BUTTON_EVT_RELEASED | BUTTON_LEFT:
handle_intermediate_left_press();
break;
@@ -342,7 +333,7 @@ unsigned int ui_tx_intermediate_step_button(
handle_intermediate_right_press();
break;
case BUTTON_EVT_RELEASED | BUTTON_LEFT | BUTTON_RIGHT:
- // Skip to confirm screen
+ // Skip to confirm screen on double press
ctx.step = Confirm;
UX_DISPLAY(ui_tx_confirm_step, NULL);
break;
@@ -351,23 +342,20 @@ unsigned int ui_tx_intermediate_step_button(
return 0;
}
-unsigned int ui_tx_confirm_step_button(
- unsigned int button_mask,
- unsigned int __attribute__ ((unused)) button_mask_counter
-) {
- switch(button_mask) {
+unsigned int ui_tx_confirm_step_button(unsigned int button_mask,
+ unsigned int __attribute__((unused))
+ button_mask_counter) {
+ switch (button_mask) {
case BUTTON_EVT_RELEASED | BUTTON_LEFT:
- if (ctx.type == Verify || ctx.type == Associate) { // Return to Senders
+ ctx.current_page = 1;
+
+ if (ctx.type == Verify) {
+ // Verify: Senders <-- Confirm
ctx.step = Senders;
- ctx.display_index = 1;
reformat_senders();
- } else if (ctx.type == TokenMint || ctx.type == TokenBurn) { // Return to Amount
- ctx.step = Amount;
- ctx.display_index = 1;
- reformat_amount();
- } else { // Return to Memo
+ } else {
+ // All (!Verify): Memo <-- Confirm
ctx.step = Memo;
- ctx.display_index = 1;
reformat_memo();
}
UX_DISPLAY(ui_tx_intermediate_step, NULL);
@@ -387,11 +375,10 @@ unsigned int ui_tx_confirm_step_button(
return 0;
}
-unsigned int ui_tx_deny_step_button(
- unsigned int button_mask,
- unsigned int __attribute__ ((unused)) button_mask_counter
-) {
- switch(button_mask) {
+unsigned int ui_tx_deny_step_button(unsigned int button_mask,
+ unsigned int __attribute__((unused))
+ button_mask_counter) {
+ switch (button_mask) {
case BUTTON_EVT_RELEASED | BUTTON_LEFT:
// Return to Confirm
ctx.step = Confirm;
@@ -409,11 +396,12 @@ unsigned int ui_tx_deny_step_button(
}
uint8_t num_screens(size_t length) {
- // Number of screens is len / display size + 1 for overflow
- if (length == 0) return 1;
-
+ // Number of screens is (len text in chars / display size in chars) + 1 for
+ // overflowing text
+ if (length <= 0) return 1;
+
uint8_t screens = length / DISPLAY_SIZE;
-
+
if (length % DISPLAY_SIZE > 0) {
screens += 1;
}
@@ -421,146 +409,172 @@ uint8_t num_screens(size_t length) {
return screens;
}
-void count_screens() {
- ctx.display_count = num_screens(strlen(ctx.full));
-}
+void count_screens_of_step() { ctx.page_count = num_screens(strlen(ctx.full)); }
-void shift_display() {
+void repaint() {
// Slide window (partial) along full entity (full) by DISPLAY_SIZE chars
explicit_bzero(ctx.partial, DISPLAY_SIZE + 1);
- memmove(
- ctx.partial,
- ctx.full + (DISPLAY_SIZE * (ctx.display_index - 1)),
- DISPLAY_SIZE
- );
+ memmove(ctx.partial, ctx.full + (DISPLAY_SIZE * (ctx.current_page - 1)),
+ DISPLAY_SIZE);
+ UX_REDISPLAY();
}
-bool last_screen() {
- return ctx.display_index == ctx.display_count;
-}
+bool last_screen_of_step() { return ctx.current_page == ctx.page_count; }
-bool first_screen() {
- return ctx.display_index == 1;
-}
+bool first_screen_of_step() { return ctx.current_page == 1; }
void reformat_operator() {
- hedera_snprintf(
- ctx.full,
- ACCOUNT_ID_SIZE,
- "%llu.%llu.%llu",
- ctx.transaction.transactionID.accountID.shardNum,
- ctx.transaction.transactionID.accountID.realmNum,
- ctx.transaction.transactionID.accountID.accountNum
- );
-
- count_screens();
-
- hedera_snprintf(
- ctx.title,
- DISPLAY_SIZE,
- "Operator (%u/%u)",
- ctx.display_index,
- ctx.display_count
- );
-
- shift_display();
+ hedera_snprintf(ctx.full, ACCOUNT_ID_SIZE, "%llu.%llu.%llu",
+ ctx.transaction.transactionID.accountID.shardNum,
+ ctx.transaction.transactionID.accountID.realmNum,
+ ctx.transaction.transactionID.accountID.account.accountNum);
+
+ count_screens_of_step();
+
+ hedera_snprintf(ctx.title, DISPLAY_SIZE, "Operator (%u/%u)",
+ ctx.current_page, ctx.page_count);
+
+ repaint();
}
void reformat_accounts(char* title_part, uint8_t transfer_index) {
- hedera_snprintf(
- ctx.full,
- ACCOUNT_ID_SIZE,
- "%llu.%llu.%llu",
- ctx.transaction.data.cryptoTransfer.transfers.accountAmounts[transfer_index].accountID.shardNum,
- ctx.transaction.data.cryptoTransfer.transfers.accountAmounts[transfer_index].accountID.realmNum,
- ctx.transaction.data.cryptoTransfer.transfers.accountAmounts[transfer_index].accountID.accountNum
- );
+ hedera_snprintf(ctx.full, ACCOUNT_ID_SIZE, "%llu.%llu.%llu",
+ ctx.transaction.data.cryptoTransfer.transfers
+ .accountAmounts[ transfer_index ]
+ .accountID.shardNum,
+ ctx.transaction.data.cryptoTransfer.transfers
+ .accountAmounts[ transfer_index ]
+ .accountID.realmNum,
+ ctx.transaction.data.cryptoTransfer.transfers
+ .accountAmounts[ transfer_index ]
+ .accountID.account.accountNum);
+
+ count_screens_of_step();
+
+ hedera_snprintf(ctx.title, DISPLAY_SIZE, "%s (%u/%u)", title_part,
+ ctx.current_page, ctx.page_count);
+}
+
+void reformat_stake_target() {
+ switch (ctx.type) {
+ case Create: {
+ if (ctx.transaction.data.cryptoCreateAccount.which_staked_id ==
+ Hedera_CryptoCreateTransactionBody_staked_account_id_tag) {
+ // An account ID and not a Node ID
+ hedera_snprintf(
+ ctx.full, ACCOUNT_ID_SIZE, "%llu.%llu.%llu",
+ ctx.transaction.data.cryptoCreateAccount.staked_id
+ .staked_account_id.shardNum,
+ ctx.transaction.data.cryptoCreateAccount.staked_id
+ .staked_account_id.realmNum,
+ ctx.transaction.data.cryptoCreateAccount.staked_id
+ .staked_account_id.account.accountNum);
+ } else if (ctx.transaction.data.cryptoCreateAccount
+ .which_staked_id ==
+ Hedera_CryptoCreateTransactionBody_staked_node_id_tag) {
+ hedera_snprintf(ctx.full, ACCOUNT_ID_SIZE, "Node %lld",
+ ctx.transaction.data.cryptoCreateAccount
+ .staked_id.staked_node_id);
+ } else {
+ hedera_snprintf(ctx.full, DISPLAY_SIZE, "%s", "None");
+ }
+ } break;
- count_screens();
+ case Update: {
+ if (ctx.transaction.data.cryptoUpdateAccount.which_staked_id ==
+ Hedera_CryptoUpdateTransactionBody_staked_account_id_tag) {
+ // An account ID and not a Node ID
+ hedera_snprintf(
+ ctx.full, ACCOUNT_ID_SIZE, "%llu.%llu.%llu",
+ ctx.transaction.data.cryptoUpdateAccount.staked_id
+ .staked_account_id.shardNum,
+ ctx.transaction.data.cryptoUpdateAccount.staked_id
+ .staked_account_id.realmNum,
+ ctx.transaction.data.cryptoUpdateAccount.staked_id
+ .staked_account_id.account.accountNum);
+ } else if (ctx.transaction.data.cryptoUpdateAccount
+ .which_staked_id ==
+ Hedera_CryptoUpdateTransactionBody_staked_node_id_tag) {
+ hedera_snprintf(ctx.full, ACCOUNT_ID_SIZE, "Node %lld",
+ ctx.transaction.data.cryptoUpdateAccount
+ .staked_id.staked_node_id);
+ } else {
+ hedera_snprintf(ctx.full, DISPLAY_SIZE, "%s", "None");
+ }
+ } break;
- hedera_snprintf(
- ctx.title,
- DISPLAY_SIZE,
- "%s (%u/%u)",
- title_part,
- ctx.display_index,
- ctx.display_count
- );
+ default:
+ break;
+ }
+
+ count_screens_of_step();
+
+ hedera_snprintf(ctx.title, DISPLAY_SIZE, "Stake To (%u/%u)",
+ ctx.current_page, ctx.page_count);
}
void reformat_token() {
switch (ctx.type) {
case Associate:
hedera_snprintf(
- ctx.full,
- ACCOUNT_ID_SIZE,
- "%llu.%llu.%llu",
- ctx.transaction.data.tokenAssociate.tokens[0].shardNum,
- ctx.transaction.data.tokenAssociate.tokens[0].realmNum,
- ctx.transaction.data.tokenAssociate.tokens[0].tokenNum
- );
+ ctx.full, ACCOUNT_ID_SIZE, "%llu.%llu.%llu",
+ ctx.transaction.data.tokenAssociate.tokens[ 0 ].shardNum,
+ ctx.transaction.data.tokenAssociate.tokens[ 0 ].realmNum,
+ ctx.transaction.data.tokenAssociate.tokens[ 0 ].tokenNum);
break;
- case TokenMint:
+ case Dissociate:
hedera_snprintf(
- ctx.full,
- ACCOUNT_ID_SIZE,
- "%llu.%llu.%llu",
- ctx.transaction.data.tokenMint.token.shardNum,
- ctx.transaction.data.tokenMint.token.realmNum,
- ctx.transaction.data.tokenMint.token.tokenNum
- );
+ ctx.full, ACCOUNT_ID_SIZE, "%llu.%llu.%llu",
+ ctx.transaction.data.tokenDissociate.tokens[ 0 ].shardNum,
+ ctx.transaction.data.tokenDissociate.tokens[ 0 ].realmNum,
+ ctx.transaction.data.tokenDissociate.tokens[ 0 ].tokenNum);
+
+ break;
+
+ case TokenMint:
+ hedera_snprintf(ctx.full, ACCOUNT_ID_SIZE, "%llu.%llu.%llu",
+ ctx.transaction.data.tokenMint.token.shardNum,
+ ctx.transaction.data.tokenMint.token.realmNum,
+ ctx.transaction.data.tokenMint.token.tokenNum);
break;
case TokenBurn:
- hedera_snprintf(
- ctx.full,
- ACCOUNT_ID_SIZE,
- "%llu.%llu.%llu",
- ctx.transaction.data.tokenBurn.token.shardNum,
- ctx.transaction.data.tokenBurn.token.realmNum,
- ctx.transaction.data.tokenBurn.token.tokenNum
- );
+ hedera_snprintf(ctx.full, ACCOUNT_ID_SIZE, "%llu.%llu.%llu",
+ ctx.transaction.data.tokenBurn.token.shardNum,
+ ctx.transaction.data.tokenBurn.token.realmNum,
+ ctx.transaction.data.tokenBurn.token.tokenNum);
break;
default:
- return;
+ break;
}
- count_screens();
+ count_screens_of_step();
- hedera_snprintf(
- ctx.title,
- DISPLAY_SIZE,
- "Token (%u/%u)",
- ctx.display_index,
- ctx.display_count
- );
+ hedera_snprintf(ctx.title, DISPLAY_SIZE, "Token (%u/%u)", ctx.current_page,
+ ctx.page_count);
}
-void reformat_tokens_accounts(char *title_part, uint8_t transfer_index) {
- hedera_snprintf(
- ctx.full,
- ACCOUNT_ID_SIZE,
- "%llu.%llu.%llu",
- ctx.transaction.data.cryptoTransfer.tokenTransfers[0].transfers[transfer_index].accountID.shardNum,
- ctx.transaction.data.cryptoTransfer.tokenTransfers[0].transfers[transfer_index].accountID.realmNum,
- ctx.transaction.data.cryptoTransfer.tokenTransfers[0].transfers[transfer_index].accountID.accountNum
- );
-
- count_screens();
-
- hedera_snprintf(
- ctx.title,
- DISPLAY_SIZE,
- "%s (%u/%u)",
- title_part,
- ctx.display_index,
- ctx.display_count
- );
+void reformat_tokens_accounts(char* title_part, uint8_t transfer_index) {
+ hedera_snprintf(ctx.full, ACCOUNT_ID_SIZE, "%llu.%llu.%llu",
+ ctx.transaction.data.cryptoTransfer.tokenTransfers[ 0 ]
+ .transfers[ transfer_index ]
+ .accountID.shardNum,
+ ctx.transaction.data.cryptoTransfer.tokenTransfers[ 0 ]
+ .transfers[ transfer_index ]
+ .accountID.realmNum,
+ ctx.transaction.data.cryptoTransfer.tokenTransfers[ 0 ]
+ .transfers[ transfer_index ]
+ .accountID.account.accountNum);
+
+ count_screens_of_step();
+
+ hedera_snprintf(ctx.title, DISPLAY_SIZE, "%s (%u/%u)", title_part,
+ ctx.current_page, ctx.page_count);
}
void reformat_senders() {
@@ -569,7 +583,13 @@ void reformat_senders() {
reformat_accounts("Account", 0);
break;
+ case Create:
+ case Update:
+ reformat_stake_target();
+ break;
+
case Associate:
+ case Dissociate:
case TokenMint:
case TokenBurn:
reformat_token();
@@ -587,11 +607,91 @@ void reformat_senders() {
return;
}
- shift_display();
+ repaint();
+}
+
+void reformat_collect_rewards() {
+ switch (ctx.type) {
+ case Create: {
+ bool declineRewards =
+ ctx.transaction.data.cryptoCreateAccount.decline_reward;
+ hedera_snprintf(ctx.full, MAX_MEMO_SIZE, "%s",
+ !declineRewards ? "Yes" : "No");
+ } break;
+
+ case Update: {
+ if (ctx.transaction.data.cryptoUpdateAccount.has_decline_reward) {
+ bool declineRewards = ctx.transaction.data.cryptoUpdateAccount
+ .decline_reward.value;
+ hedera_snprintf(ctx.full, MAX_MEMO_SIZE, "%s",
+ declineRewards ? "No" : "Yes");
+ } else {
+ hedera_snprintf(ctx.full, MAX_MEMO_SIZE, "%s", "-");
+ }
+ } break;
+
+ default:
+ break;
+ }
+
+ count_screens_of_step(); // 1
+
+ hedera_snprintf(ctx.title, DISPLAY_SIZE, "Collect Rewards?",
+ ctx.current_page, ctx.page_count);
+
+ repaint();
+}
+
+void reformat_target_account() {
+ switch (ctx.type) {
+ case Associate: {
+ bool hasAccount = ctx.transaction.data.tokenAssociate.has_account;
+ if (hasAccount) {
+ hedera_snprintf(
+ ctx.full, ACCOUNT_ID_SIZE, "%llu.%llu.%llu",
+ ctx.transaction.data.tokenAssociate.account.shardNum,
+ ctx.transaction.data.tokenAssociate.account.realmNum,
+ ctx.transaction.data.tokenAssociate.account.account
+ .accountNum);
+ } else {
+ hedera_snprintf(
+ ctx.full, ACCOUNT_ID_SIZE, "%llu.%llu.%llu",
+ ctx.transaction.transactionID.accountID.shardNum,
+ ctx.transaction.transactionID.accountID.realmNum,
+ ctx.transaction.transactionID.accountID.account.accountNum);
+ }
+ } break;
+
+ case Dissociate: {
+ bool hasAccount = ctx.transaction.data.tokenDissociate.has_account;
+ if (hasAccount) {
+ hedera_snprintf(
+ ctx.full, ACCOUNT_ID_SIZE, "%llu.%llu.%llu",
+ ctx.transaction.data.tokenDissociate.account.shardNum,
+ ctx.transaction.data.tokenDissociate.account.realmNum,
+ ctx.transaction.data.tokenDissociate.account.account
+ .accountNum);
+ } else {
+ hedera_snprintf(
+ ctx.full, ACCOUNT_ID_SIZE, "%llu.%llu.%llu",
+ ctx.transaction.transactionID.accountID.shardNum,
+ ctx.transaction.transactionID.accountID.realmNum,
+ ctx.transaction.transactionID.accountID.account.accountNum);
+ }
+ } break;
+
+ default:
+ break;
+ }
}
void reformat_recipients() {
switch (ctx.type) {
+ case Create:
+ case Update:
+ reformat_collect_rewards();
+ break;
+
case TokenTransfer:
reformat_tokens_accounts("Recipient", ctx.transfer_to_index);
break;
@@ -604,252 +704,245 @@ void reformat_recipients() {
return;
}
- shift_display();
+ repaint();
}
void reformat_amount() {
switch (ctx.type) {
case Create:
hedera_snprintf(
- ctx.full,
- DISPLAY_SIZE * 3,
- "%s hbar",
- hedera_format_tinybar(ctx.transaction.data.cryptoCreateAccount.initialBalance)
- );
+ ctx.full, DISPLAY_SIZE * 3, "%s hbar",
+ hedera_format_tinybar(
+ ctx.transaction.data.cryptoCreateAccount.initialBalance));
break;
+ case Update: {
+ if (ctx.transaction.data.cryptoUpdateAccount
+ .has_accountIDToUpdate) {
+ hedera_snprintf(ctx.full, ACCOUNT_ID_SIZE, "%llu.%llu.%llu",
+ ctx.transaction.data.cryptoUpdateAccount
+ .accountIDToUpdate.shardNum,
+ ctx.transaction.data.cryptoUpdateAccount
+ .accountIDToUpdate.realmNum,
+ ctx.transaction.data.cryptoUpdateAccount
+ .accountIDToUpdate.account.accountNum);
+ } else {
+ hedera_snprintf(
+ ctx.full, ACCOUNT_ID_SIZE, "%llu.%llu.%llu",
+ ctx.transaction.transactionID.accountID.shardNum,
+ ctx.transaction.transactionID.accountID.realmNum,
+ ctx.transaction.transactionID.accountID.account.accountNum);
+ }
+ } break;
+
case Transfer:
- hedera_snprintf(
- ctx.full,
- DISPLAY_SIZE * 3,
- "%s hbar",
- hedera_format_tinybar(ctx.transaction.data.cryptoTransfer.transfers.accountAmounts[ctx.transfer_to_index].amount)
- );
+ hedera_snprintf(ctx.full, DISPLAY_SIZE * 3, "%s hbar",
+ hedera_format_tinybar(
+ ctx.transaction.data.cryptoTransfer.transfers
+ .accountAmounts[ ctx.transfer_to_index ]
+ .amount));
break;
case TokenMint:
- validate_decimals(ctx.transaction.data.tokenMint.expected_decimals.value);
hedera_snprintf(
- ctx.full,
- DISPLAY_SIZE * 3,
- "%s",
+ ctx.full, DISPLAY_SIZE * 3, "%s",
hedera_format_amount(
ctx.transaction.data.tokenMint.amount,
- ctx.transaction.data.tokenMint.expected_decimals.value
- )
- );
+ 0)); // Must be lowest denomination without decimals
break;
case TokenBurn:
- validate_decimals(ctx.transaction.data.tokenBurn.expected_decimals.value);
hedera_snprintf(
- ctx.full,
- DISPLAY_SIZE * 3,
- "%s",
+ ctx.full, DISPLAY_SIZE * 3, "%s",
hedera_format_amount(
ctx.transaction.data.tokenBurn.amount,
- ctx.transaction.data.tokenBurn.expected_decimals.value
- )
- );
+ 0)); // Must be lowest denomination without decimals
break;
+ case Associate:
+ case Dissociate:
+ reformat_target_account();
+ break;
+
case TokenTransfer:
- validate_decimals(ctx.transaction.data.cryptoTransfer.tokenTransfers[0].expected_decimals.value);
hedera_snprintf(
- ctx.full,
- DISPLAY_SIZE * 3,
- "%s",
+ ctx.full, DISPLAY_SIZE * 3, "%s",
hedera_format_amount(
- ctx.transaction.data.cryptoTransfer.tokenTransfers[0].transfers[ctx.transfer_to_index].amount,
- ctx.transaction.data.cryptoTransfer.tokenTransfers[0].expected_decimals.value
- )
- );
+ ctx.transaction.data.cryptoTransfer.tokenTransfers[ 0 ]
+ .transfers[ ctx.transfer_to_index ]
+ .amount,
+ ctx.transaction.data.cryptoTransfer.tokenTransfers[ 0 ]
+ .expected_decimals.value));
break;
default:
- return;
+ break;
}
- count_screens();
+ count_screens_of_step();
- hedera_snprintf(
- ctx.title,
- DISPLAY_SIZE,
- "%s (%u/%u)",
- ctx.type == Create ? "Balance" : "Amount",
- ctx.display_index,
- ctx.display_count
- );
-
- shift_display();
+ switch (ctx.type) {
+ case Update:
+ case Associate:
+ case Dissociate:
+ hedera_snprintf(ctx.title, DISPLAY_SIZE, "%s (%u/%u)", "Updating",
+ ctx.current_page, ctx.page_count);
+ break;
+ case Create:
+ hedera_snprintf(ctx.title, DISPLAY_SIZE, "%s (%u/%u)", "Balance",
+ ctx.current_page, ctx.page_count);
+ break;
+ default:
+ hedera_snprintf(ctx.title, DISPLAY_SIZE, "%s (%u/%u)", "Amount",
+ ctx.current_page, ctx.page_count);
+ break;
+ }
+
+ repaint();
}
void reformat_fee() {
- hedera_snprintf(
- ctx.full,
- DISPLAY_SIZE * 3,
- "%s hbar",
- hedera_format_tinybar(ctx.transaction.transactionFee)
- );
+ hedera_snprintf(ctx.full, DISPLAY_SIZE * 3, "%s hbar",
+ hedera_format_tinybar(ctx.transaction.transactionFee));
- count_screens();
+ count_screens_of_step();
- hedera_snprintf(
- ctx.title,
- DISPLAY_SIZE,
- "Max Fee (%u/%u)",
- ctx.display_index,
- ctx.display_count
- );
-
- shift_display();
+ hedera_snprintf(ctx.title, DISPLAY_SIZE, "Max Fee (%u/%u)",
+ ctx.current_page, ctx.page_count);
+
+ repaint();
}
void reformat_memo() {
hedera_snprintf(
- ctx.full,
- MAX_MEMO_SIZE,
- "%s",
- strlen(ctx.transaction.memo) > 0 ? ctx.transaction.memo : ""
- );
-
- if (strlen(ctx.full) > MAX_MEMO_SIZE) {
- // :grimacing:
- THROW(EXCEPTION_MALFORMED_APDU);
- }
+ ctx.full, MAX_MEMO_SIZE, "%s",
+ strlen(ctx.transaction.memo) > 0 ? ctx.transaction.memo : "");
- count_screens();
+ count_screens_of_step();
- hedera_snprintf(
- ctx.title,
- DISPLAY_SIZE,
- "Memo (%u/%u)",
- ctx.display_index,
- ctx.display_count
- );
-
- shift_display();
+ hedera_snprintf(ctx.title, DISPLAY_SIZE, "Memo (%u/%u)", ctx.current_page,
+ ctx.page_count);
+
+ repaint();
}
void handle_transaction_body() {
explicit_bzero(ctx.summary_line_1, DISPLAY_SIZE + 1);
explicit_bzero(ctx.summary_line_2, DISPLAY_SIZE + 1);
- explicit_bzero(ctx.full, ACCOUNT_ID_SIZE + 1);
+ explicit_bzero(ctx.full, DISPLAY_SIZE * 3 + 1);
explicit_bzero(ctx.partial, DISPLAY_SIZE + 1);
// Step 1, Unknown Type, Screen 1 of 1
ctx.step = Summary;
ctx.type = Unknown;
- ctx.display_index = 1;
- ctx.display_count = 1;
+ ctx.current_page = 1;
+ ctx.page_count = 1;
- //
+ //
// with Key #X?
- hedera_snprintf(
- ctx.summary_line_2,
- DISPLAY_SIZE,
- "with Key #%u?",
- ctx.key_index
- );
+ hedera_snprintf(ctx.summary_line_2, DISPLAY_SIZE, "with Key #%u?",
+ ctx.key_index);
// Handle parsed protobuf message of transaction body
switch (ctx.transaction.which_data) {
- case HederaTransactionBody_cryptoCreateAccount_tag:
- // Create Account Transaction
+ case Hedera_TransactionBody_cryptoCreateAccount_tag:
ctx.type = Create;
- hedera_snprintf(
- ctx.summary_line_1,
- DISPLAY_SIZE,
- "Create Account"
- );
+ hedera_snprintf(ctx.summary_line_1, DISPLAY_SIZE, "Create Account");
+ break;
+ case Hedera_TransactionBody_cryptoUpdateAccount_tag:
+ ctx.type = Update;
+ hedera_snprintf(ctx.summary_line_1, DISPLAY_SIZE, "Update Account");
break;
- case HederaTransactionBody_tokenAssociate_tag:
+ case Hedera_TransactionBody_tokenAssociate_tag:
ctx.type = Associate;
- hedera_snprintf(
- ctx.summary_line_1,
- DISPLAY_SIZE,
- "Associate Token"
- );
+ hedera_snprintf(ctx.summary_line_1, DISPLAY_SIZE,
+ "Associate Token");
+ break;
+ case Hedera_TransactionBody_tokenDissociate_tag:
+ ctx.type = Dissociate;
+ hedera_snprintf(ctx.summary_line_1, DISPLAY_SIZE,
+ "Dissociate Token");
break;
- case HederaTransactionBody_tokenMint_tag:
+ case Hedera_TransactionBody_tokenMint_tag:
ctx.type = TokenMint;
- hedera_snprintf(
- ctx.summary_line_1,
- DISPLAY_SIZE,
- "Mint Token"
- );
-
+ hedera_snprintf(ctx.summary_line_1, DISPLAY_SIZE, "Mint Token");
break;
- case HederaTransactionBody_tokenBurn_tag:
+ case Hedera_TransactionBody_tokenBurn_tag:
ctx.type = TokenBurn;
- hedera_snprintf(
- ctx.summary_line_1,
- DISPLAY_SIZE,
- "Burn Token"
- );
-
+ hedera_snprintf(ctx.summary_line_1, DISPLAY_SIZE, "Burn Token");
break;
- case HederaTransactionBody_cryptoTransfer_tag: {
- validate_transfer();
-
- if ( // Only 1 Account (Sender), Fee 1 Tinybar, and Value 0 Tinybar
- ctx.transaction.data.cryptoTransfer.transfers.accountAmounts[0].amount == 0 &&
- ctx.transaction.data.cryptoTransfer.transfers.accountAmounts_count == 1 &&
- ctx.transaction.transactionFee == 1
- ) {
- // Verify Account Transaction
+ case Hedera_TransactionBody_cryptoTransfer_tag: {
+ if (
+ /*
+ * "Verify Account Transaction"
+ *
+ * This is an arbitary transfer transaction that is designed
+ * to fail always. Transfer 0 hbar to no-one with a max fee
+ * of 1 tinybar. If this transaction fails with
+ * "insufficient fee" rather than "invalid signature", then
+ * we know that the signature provided by this key is indeed
+ * associated with the operator account for the transaction.
+ */
+ ctx.transaction.data.cryptoTransfer.transfers
+ .accountAmounts[ 0 ]
+ .amount == 0 &&
+ ctx.transaction.data.cryptoTransfer.transfers
+ .accountAmounts_count == 1 &&
+ ctx.transaction.transactionFee == 1) {
ctx.type = Verify;
- hedera_snprintf(
- ctx.summary_line_1,
- DISPLAY_SIZE,
- "Verify Account"
- );
- } else if (ctx.transaction.data.cryptoTransfer.transfers.accountAmounts_count == 2) {
- // Number of Accounts == 2
- // Some other Transfer Transaction
+ hedera_snprintf(ctx.summary_line_1, DISPLAY_SIZE,
+ "Verify Account");
+ } else if (ctx.transaction.data.cryptoTransfer.transfers
+ .accountAmounts_count == 2) {
+ // Hbar Transfer between two accounts
ctx.type = Transfer;
- hedera_snprintf(
- ctx.summary_line_1,
- DISPLAY_SIZE,
- "Send Hbar"
- );
+ hedera_snprintf(ctx.summary_line_1, DISPLAY_SIZE, "Send Hbar");
- // Determine Sender based on amount
+ // Determine Sender based on transfers.accountAmounts
ctx.transfer_to_index = 1;
ctx.transfer_from_index = 0;
- if (ctx.transaction.data.cryptoTransfer.transfers.accountAmounts[0].amount > 0) {
+ if (ctx.transaction.data.cryptoTransfer.transfers
+ .accountAmounts[ 0 ]
+ .amount > 0) {
ctx.transfer_to_index = 0;
ctx.transfer_from_index = 1;
}
- } else if (ctx.transaction.data.cryptoTransfer.tokenTransfers_count == 1) {
+ } else if (ctx.transaction.data.cryptoTransfer
+ .tokenTransfers_count == 1) {
+ // Fungible Token Transfer (two token transfers with one
+ // transfer each)
ctx.type = TokenTransfer;
+ validate_token_transfer();
+
hedera_snprintf(
- ctx.summary_line_1,
- DISPLAY_SIZE,
- "Send %llu.%llu.%llu",
- ctx.transaction.data.cryptoTransfer.tokenTransfers[0].token.shardNum,
- ctx.transaction.data.cryptoTransfer.tokenTransfers[0].token.realmNum,
- ctx.transaction.data.cryptoTransfer.tokenTransfers[0].token.tokenNum
- );
+ ctx.summary_line_1, DISPLAY_SIZE, "Send %llu.%llu.%llu",
+ ctx.transaction.data.cryptoTransfer.tokenTransfers[ 0 ]
+ .token.shardNum,
+ ctx.transaction.data.cryptoTransfer.tokenTransfers[ 0 ]
+ .token.realmNum,
+ ctx.transaction.data.cryptoTransfer.tokenTransfers[ 0 ]
+ .token.tokenNum);
// Determine Sender based on amount
ctx.transfer_from_index = 0;
ctx.transfer_to_index = 1;
- if (ctx.transaction.data.cryptoTransfer.tokenTransfers[0].transfers[0].amount > 0) {
+ if (ctx.transaction.data.cryptoTransfer.tokenTransfers[ 0 ]
+ .transfers[ 0 ]
+ .amount > 0) {
ctx.transfer_from_index = 1;
ctx.transfer_to_index = 0;
}
@@ -862,52 +955,13 @@ void handle_transaction_body() {
default:
// Unsupported
THROW(EXCEPTION_MALFORMED_APDU);
+ break;
}
UX_DISPLAY(ui_tx_summary_step, NULL);
}
#elif defined(TARGET_NANOX) || defined(TARGET_NANOS2)
-
-static struct sign_tx_context_t {
- // ui common
- uint32_t key_index;
- uint8_t transfer_from_index;
- uint8_t transfer_to_index;
-
- // Transaction Summary
- char summary_line_1[DISPLAY_SIZE + 1];
- char summary_line_2[DISPLAY_SIZE + 1];
- char senders_title[DISPLAY_SIZE + 1];
- char amount_title[DISPLAY_SIZE + 1];
- char partial[DISPLAY_SIZE + 1];
-
- enum TransactionType type;
-
- // Transaction Operator
- char operator[DISPLAY_SIZE * 2 + 1];
-
- // Transaction Senders
- char senders[DISPLAY_SIZE * 2 + 1];
-
- // Transaction Recipients
- char recipients[DISPLAY_SIZE * 2 + 1];
-
- // Transaction Amount
- char amount[DISPLAY_SIZE * 2 + 1];
-
- // Transaction Fee
- char fee[DISPLAY_SIZE * 2 + 1];
-
- // Transaction Memo
- char memo[MAX_MEMO_SIZE + 1];
-
- // Parsed transaction
- HederaTransactionBody transaction;
-} ctx;
-
-// UI Definition for Nano X
-
// Confirm Callback
unsigned int io_seproxyhal_tx_approve(const bagl_element_t* e) {
io_exchange_with_code(EXCEPTION_OK, 64);
@@ -922,15 +976,8 @@ unsigned int io_seproxyhal_tx_reject(const bagl_element_t* e) {
return 0;
}
-UX_STEP_NOCB(
- ux_tx_flow_1_step,
- bnn,
- {
- "Transaction Summary",
- ctx.summary_line_1,
- ctx.summary_line_2
- }
-);
+UX_STEP_NOCB(ux_tx_flow_1_step, bnn,
+ {"Transaction Summary", ctx.summary_line_1, ctx.summary_line_2});
UX_STEP_NOCB(
ux_tx_flow_2_step,
@@ -941,111 +988,75 @@ UX_STEP_NOCB(
}
);
-UX_STEP_NOCB(
- ux_tx_flow_3_step,
- bnnn_paging,
- {
- .title = (char*) ctx.senders_title,
- .text = (char*) ctx.senders
- }
-);
+UX_STEP_NOCB(ux_tx_flow_3_step, bnnn_paging,
+ {.title = (char*)ctx.senders_title, .text = (char*)ctx.senders});
-UX_STEP_NOCB(
- ux_tx_flow_4_step,
- bnnn_paging,
- {
- .title = "Recipient",
- .text = (char*) ctx.recipients
- }
-);
+UX_STEP_NOCB(ux_tx_flow_4_step, bnnn_paging,
+ {.title = (char*)ctx.recipients_title,
+ .text = (char*)ctx.recipients});
-UX_STEP_NOCB(
- ux_tx_flow_5_step,
- bnnn_paging,
- {
- .title = (char*) ctx.amount_title,
- .text = (char*) ctx.amount
- }
-);
+UX_STEP_NOCB(ux_tx_flow_5_step, bnnn_paging,
+ {.title = (char*)ctx.amount_title, .text = (char*)ctx.amount});
-UX_STEP_NOCB(
- ux_tx_flow_6_step,
- bnnn_paging,
- {
- .title = "Max Fee",
- .text = (char*) ctx.fee
- }
-);
+UX_STEP_NOCB(ux_tx_flow_6_step, bnnn_paging,
+ {.title = "Max Fee", .text = (char*)ctx.fee});
-UX_STEP_NOCB(
- ux_tx_flow_7_step,
- bnnn_paging,
- {
- .title = "Memo",
- .text = (char*) ctx.memo
- }
-);
+UX_STEP_NOCB(ux_tx_flow_7_step, bnnn_paging,
+ {.title = "Memo", .text = (char*)ctx.memo});
-UX_STEP_VALID(
- ux_tx_flow_8_step,
- pb,
- io_seproxyhal_tx_approve(NULL),
- {
- &C_icon_validate_14,
- "Confirm"
- }
-);
+UX_STEP_VALID(ux_tx_flow_8_step, pb, io_seproxyhal_tx_approve(NULL),
+ {&C_icon_validate_14, "Confirm"});
-UX_STEP_VALID(
- ux_tx_flow_9_step,
- pb,
- io_seproxyhal_tx_reject(NULL),
- {
- &C_icon_crossmark,
- "Reject"
- }
-);
+UX_STEP_VALID(ux_tx_flow_9_step, pb, io_seproxyhal_tx_reject(NULL),
+ {&C_icon_crossmark, "Reject"});
// Transfer UX Flow
-UX_DEF(
- ux_transfer_flow,
- &ux_tx_flow_1_step,
- &ux_tx_flow_2_step,
- &ux_tx_flow_3_step,
- &ux_tx_flow_4_step,
- &ux_tx_flow_5_step,
- &ux_tx_flow_6_step,
- &ux_tx_flow_7_step,
- &ux_tx_flow_8_step,
- &ux_tx_flow_9_step
-);
+// Summary, Operator, Senders, Recipients, Amount, Fee, Memo, Confirm, Deny
+UX_DEF(ux_transfer_flow, &ux_tx_flow_1_step, &ux_tx_flow_2_step,
+ &ux_tx_flow_3_step, &ux_tx_flow_4_step, &ux_tx_flow_5_step,
+ &ux_tx_flow_6_step, &ux_tx_flow_7_step, &ux_tx_flow_8_step,
+ &ux_tx_flow_9_step);
// Create UX Flow
-UX_DEF(
- ux_create_flow,
- &ux_tx_flow_1_step,
- &ux_tx_flow_2_step,
- &ux_tx_flow_5_step,
- &ux_tx_flow_6_step,
- &ux_tx_flow_7_step,
- &ux_tx_flow_8_step,
- &ux_tx_flow_9_step
-);
+// Summary, Operator, Senders (Stake To), Recipients (Collect Rewards), Amount
+// (Initial Balance), Fee, Memo, Confirm, Deny
+UX_DEF(ux_create_flow, &ux_tx_flow_1_step, &ux_tx_flow_2_step,
+ &ux_tx_flow_3_step, &ux_tx_flow_4_step, &ux_tx_flow_5_step,
+ &ux_tx_flow_6_step, &ux_tx_flow_7_step, &ux_tx_flow_8_step,
+ &ux_tx_flow_9_step);
+
+// Update UX Flow
+// Summary, Operator, Senders (Stake To), Recipients (Collect Rewards), Amount
+// (Updated Account), Fee, Memo, Confirm, Deny
+UX_DEF(ux_update_flow, &ux_tx_flow_1_step, &ux_tx_flow_2_step,
+ &ux_tx_flow_3_step, &ux_tx_flow_4_step, &ux_tx_flow_5_step,
+ &ux_tx_flow_6_step, &ux_tx_flow_7_step, &ux_tx_flow_8_step,
+ &ux_tx_flow_9_step);
// Verify UX Flow
-UX_DEF(
- ux_verify_flow,
- &ux_tx_flow_1_step,
- &ux_tx_flow_3_step,
- &ux_tx_flow_8_step,
- &ux_tx_flow_9_step
-);
+// Summary, Senders (Account), Confirm, Deny
+UX_DEF(ux_verify_flow, &ux_tx_flow_1_step, &ux_tx_flow_3_step,
+ &ux_tx_flow_8_step, &ux_tx_flow_9_step);
+
+// TokenMint, TokenBurn
+// Summary, Operator, Senders (Token), Amount, Fee, Memo, Confirm, Deny
+UX_DEF(ux_mint_flow, &ux_tx_flow_1_step, &ux_tx_flow_2_step, &ux_tx_flow_4_step,
+ &ux_tx_flow_5_step, &ux_tx_flow_6_step, &ux_tx_flow_7_step,
+ &ux_tx_flow_8_step, &ux_tx_flow_9_step);
+
+// Associate, Dissociate
+// Summary, Operator, Senders (Token), Recipients (Account), Fee, Memo, Confirm,
+// Deny
+UX_DEF(ux_associate_flow, &ux_tx_flow_1_step, &ux_tx_flow_2_step,
+ &ux_tx_flow_3_step, &ux_tx_flow_4_step, &ux_tx_flow_6_step,
+ &ux_tx_flow_7_step, &ux_tx_flow_8_step, &ux_tx_flow_9_step);
void handle_transaction_body() {
explicit_bzero(ctx.summary_line_1, DISPLAY_SIZE + 1);
explicit_bzero(ctx.summary_line_2, DISPLAY_SIZE + 1);
explicit_bzero(ctx.amount_title, DISPLAY_SIZE + 1);
explicit_bzero(ctx.senders_title, DISPLAY_SIZE + 1);
+ explicit_bzero(ctx.recipients_title, DISPLAY_SIZE + 1);
explicit_bzero(ctx.operator, DISPLAY_SIZE * 2 + 1);
explicit_bzero(ctx.senders, DISPLAY_SIZE * 2 + 1);
explicit_bzero(ctx.recipients, DISPLAY_SIZE * 2 + 1);
@@ -1055,219 +1066,354 @@ void handle_transaction_body() {
ctx.type = Unknown;
- //
+ //
// with Key #X?
- hedera_snprintf(
- ctx.summary_line_2,
- DISPLAY_SIZE,
- "with Key #%u?",
- ctx.key_index
- );
+ hedera_snprintf(ctx.summary_line_2, DISPLAY_SIZE, "with Key #%u?",
+ ctx.key_index);
- hedera_snprintf(
- ctx.operator,
- DISPLAY_SIZE * 2,
- "%llu.%llu.%llu",
- ctx.transaction.transactionID.accountID.shardNum,
- ctx.transaction.transactionID.accountID.realmNum,
- ctx.transaction.transactionID.accountID.accountNum
- );
+ hedera_snprintf(ctx.operator, DISPLAY_SIZE * 2, "%llu.%llu.%llu",
+ ctx.transaction.transactionID.accountID.shardNum,
+ ctx.transaction.transactionID.accountID.realmNum,
+ ctx.transaction.transactionID.accountID.account);
- hedera_snprintf(
- ctx.fee,
- DISPLAY_SIZE * 2,
- "%s hbar",
- hedera_format_tinybar(ctx.transaction.transactionFee)
- );
+ hedera_snprintf(ctx.fee, DISPLAY_SIZE * 2, "%s hbar",
+ hedera_format_tinybar(ctx.transaction.transactionFee));
- hedera_snprintf(
- ctx.memo,
- MAX_MEMO_SIZE,
- "%s",
- ctx.transaction.memo
- );
-
- hedera_sprintf(
- ctx.amount_title,
- "Amount"
- );
-
- hedera_sprintf(
- ctx.senders_title,
- "Sender"
- );
+ hedera_snprintf(ctx.memo, MAX_MEMO_SIZE, "%s", ctx.transaction.memo);
+
+ hedera_sprintf(ctx.amount_title, "Amount");
+ hedera_sprintf(ctx.senders_title, "Sender");
+ hedera_sprintf(ctx.recipients_title, "Recipient");
// Handle parsed protobuf message of transaction body
switch (ctx.transaction.which_data) {
- case HederaTransactionBody_cryptoCreateAccount_tag:
+ case Hedera_TransactionBody_cryptoCreateAccount_tag: {
ctx.type = Create;
- // Create Account Transaction
- hedera_sprintf(
- ctx.summary_line_1,
- "Create Account"
- );
- hedera_sprintf(
- ctx.amount_title,
- "Balance"
- );
+ hedera_sprintf(ctx.summary_line_1, "Create Account");
+ hedera_sprintf(ctx.senders_title, "Stake To");
+ hedera_sprintf(ctx.recipients_title, "Collect Rewards?");
+ hedera_sprintf(ctx.amount_title, "Balance");
+
+ char stake_target[ ACCOUNT_ID_SIZE ];
+
+ if (ctx.transaction.data.cryptoCreateAccount.which_staked_id ==
+ Hedera_CryptoCreateTransactionBody_staked_account_id_tag) {
+ hedera_snprintf(stake_target, ACCOUNT_ID_SIZE, "%llu.%llu.%llu",
+ ctx.transaction.data.cryptoCreateAccount
+ .staked_id.staked_account_id.shardNum,
+ ctx.transaction.data.cryptoCreateAccount
+ .staked_id.staked_account_id.realmNum,
+ ctx.transaction.data.cryptoCreateAccount
+ .staked_id.staked_account_id.account);
+ } else if (ctx.transaction.data.cryptoCreateAccount
+ .which_staked_id ==
+ Hedera_CryptoCreateTransactionBody_staked_node_id_tag) {
+ hedera_snprintf(stake_target, ACCOUNT_ID_SIZE, "Node %lld",
+ ctx.transaction.data.cryptoCreateAccount
+ .staked_id.staked_node_id);
+ } else {
+ hedera_snprintf(stake_target, DISPLAY_SIZE * 2, "None");
+ }
+
+ hedera_snprintf(ctx.senders, DISPLAY_SIZE * 2, "%s", stake_target);
+
hedera_snprintf(
- ctx.amount,
- DISPLAY_SIZE * 2,
- "%s hbar",
- hedera_format_tinybar(ctx.transaction.data.cryptoCreateAccount.initialBalance)
- );
- break;
+ ctx.recipients, DISPLAY_SIZE * 2, "%s",
+ ctx.transaction.data.cryptoCreateAccount.decline_reward
+ ? "No"
+ : "Yes");
- case HederaTransactionBody_tokenAssociate_tag:
+ hedera_snprintf(
+ ctx.amount, DISPLAY_SIZE * 2, "%s hbar",
+ hedera_format_tinybar(
+ ctx.transaction.data.cryptoCreateAccount.initialBalance));
+ } break;
+
+ case Hedera_TransactionBody_cryptoUpdateAccount_tag: {
+ ctx.type = Update;
+ hedera_sprintf(ctx.summary_line_1, "Update Account");
+ hedera_sprintf(ctx.senders_title, "Stake To");
+ hedera_sprintf(ctx.recipients_title, "Collect Rewards");
+ hedera_sprintf(ctx.amount_title, "Updating");
+
+ const char stake_target[ DISPLAY_SIZE * 2 ];
+
+ if (ctx.transaction.data.cryptoUpdateAccount.which_staked_id ==
+ Hedera_CryptoUpdateTransactionBody_staked_account_id_tag) {
+ hedera_snprintf(stake_target, DISPLAY_SIZE * 2,
+ "%llu.%llu.%llu",
+ ctx.transaction.data.cryptoUpdateAccount
+ .staked_id.staked_account_id.shardNum,
+ ctx.transaction.data.cryptoUpdateAccount
+ .staked_id.staked_account_id.realmNum,
+ ctx.transaction.data.cryptoUpdateAccount
+ .staked_id.staked_account_id.account);
+ } else if (ctx.transaction.data.cryptoUpdateAccount
+ .which_staked_id ==
+ Hedera_CryptoUpdateTransactionBody_staked_node_id_tag) {
+ hedera_snprintf(stake_target, ACCOUNT_ID_SIZE, "Node %lld",
+ ctx.transaction.data.cryptoUpdateAccount
+ .staked_id.staked_node_id);
+ } else {
+ hedera_snprintf(stake_target, DISPLAY_SIZE * 2, "None");
+ }
+
+ hedera_snprintf(ctx.senders, DISPLAY_SIZE * 2, "%s", stake_target);
+
+ if (ctx.transaction.data.cryptoUpdateAccount.has_decline_reward) {
+ bool declineRewards = ctx.transaction.data.cryptoUpdateAccount
+ .decline_reward.value;
+ hedera_snprintf(ctx.recipients, DISPLAY_SIZE, "%s",
+ declineRewards ? "No" : "Yes");
+ } else {
+ hedera_snprintf(ctx.recipients, DISPLAY_SIZE, "%s", "-");
+ }
+
+ if (ctx.transaction.data.cryptoUpdateAccount
+ .has_accountIDToUpdate) {
+ Hedera_AccountID updatedAccount =
+ ctx.transaction.data.cryptoUpdateAccount.accountIDToUpdate;
+ hedera_snprintf(ctx.amount, DISPLAY_SIZE * 2, "%llu.%llu.%llu",
+ updatedAccount.shardNum,
+ updatedAccount.realmNum,
+ updatedAccount.account);
+ } else {
+ hedera_snprintf(
+ ctx.amount, DISPLAY_SIZE * 2, "%llu.%llu.%llu",
+ ctx.transaction.transactionID.accountID.shardNum,
+ ctx.transaction.transactionID.accountID.realmNum,
+ ctx.transaction.transactionID.accountID.account);
+ }
+ } break;
+
+ case Hedera_TransactionBody_tokenAssociate_tag: {
ctx.type = Associate;
- hedera_sprintf(
- ctx.summary_line_1,
- "Associate Token"
- );
+ hedera_sprintf(ctx.summary_line_1, "Associate Token");
+ hedera_sprintf(ctx.senders_title, "Token");
+ hedera_sprintf(ctx.recipients_title, "Account");
+
+ hedera_snprintf(
+ ctx.senders, DISPLAY_SIZE * 2, "%llu.%llu.%llu",
+ ctx.transaction.data.tokenAssociate.tokens[ 0 ].shardNum,
+ ctx.transaction.data.tokenAssociate.tokens[ 0 ].realmNum,
+ ctx.transaction.data.tokenAssociate.tokens[ 0 ].tokenNum);
+
+ bool hasAccount = ctx.transaction.data.tokenAssociate.has_account;
+
+ if (hasAccount) {
+ hedera_snprintf(
+ ctx.recipients, ACCOUNT_ID_SIZE, "%llu.%llu.%llu",
+ ctx.transaction.data.tokenAssociate.account.shardNum,
+ ctx.transaction.data.tokenAssociate.account.realmNum,
+ ctx.transaction.data.tokenAssociate.account.account
+ .accountNum);
+ } else {
+ hedera_snprintf(
+ ctx.recipients, ACCOUNT_ID_SIZE, "%llu.%llu.%llu",
+ ctx.transaction.transactionID.accountID.shardNum,
+ ctx.transaction.transactionID.accountID.realmNum,
+ ctx.transaction.transactionID.accountID.account.accountNum);
+ }
+ } break;
+
+ case Hedera_TransactionBody_tokenDissociate_tag: {
+ ctx.type = Dissociate;
- hedera_sprintf(
- ctx.senders_title,
- "Token");
+ hedera_sprintf(ctx.summary_line_1, "Dissociate Token");
+ hedera_sprintf(ctx.senders_title, "Token");
+ hedera_sprintf(ctx.recipients_title, "Account");
hedera_snprintf(
- ctx.senders,
- DISPLAY_SIZE * 2,
- "%llu.%llu.%llu",
- ctx.transaction.data.cryptoTransfer.tokenTransfers[0].token.shardNum,
- ctx.transaction.data.cryptoTransfer.tokenTransfers[0].token.realmNum,
- ctx.transaction.data.cryptoTransfer.tokenTransfers[0].token.tokenNum
- );
+ ctx.senders, DISPLAY_SIZE * 2, "%llu.%llu.%llu",
+ ctx.transaction.data.tokenDissociate.tokens[ 0 ].shardNum,
+ ctx.transaction.data.tokenDissociate.tokens[ 0 ].realmNum,
+ ctx.transaction.data.tokenDissociate.tokens[ 0 ].tokenNum);
- break;
+ bool hasAccount = ctx.transaction.data.tokenAssociate.has_account;
+
+ if (hasAccount) {
+ hedera_snprintf(
+ ctx.recipients, ACCOUNT_ID_SIZE, "%llu.%llu.%llu",
+ ctx.transaction.data.tokenDissociate.account.shardNum,
+ ctx.transaction.data.tokenDissociate.account.realmNum,
+ ctx.transaction.data.tokenDissociate.account.account
+ .accountNum);
+ } else {
+ hedera_snprintf(
+ ctx.recipients, ACCOUNT_ID_SIZE, "%llu.%llu.%llu",
+ ctx.transaction.transactionID.accountID.shardNum,
+ ctx.transaction.transactionID.accountID.realmNum,
+ ctx.transaction.transactionID.accountID.account.accountNum);
+ }
+ } break;
+
+ case Hedera_TransactionBody_tokenMint_tag: {
+ ctx.type = TokenMint;
+
+ hedera_sprintf(ctx.summary_line_1, "Mint Token");
+
+ hedera_sprintf(ctx.senders_title, "Token");
+
+ hedera_snprintf(ctx.senders, DISPLAY_SIZE * 2, "%llu.%llu.%llu",
+ ctx.transaction.data.tokenMint.token.shardNum,
+ ctx.transaction.data.tokenMint.token.realmNum,
+ ctx.transaction.data.tokenMint.token.tokenNum);
+
+ hedera_snprintf(
+ ctx.amount, DISPLAY_SIZE * 2, "%s",
+ hedera_format_amount(ctx.transaction.data.tokenMint.amount,
+ 0)); // always lowest denomination of token
+ } break;
+
+ case Hedera_TransactionBody_tokenBurn_tag: {
+ ctx.type = TokenBurn;
- case HederaTransactionBody_cryptoTransfer_tag: {
- validate_transfer();
+ hedera_sprintf(ctx.summary_line_1, "Burn Token");
+ hedera_sprintf(ctx.senders_title, "Token");
+
+ hedera_snprintf(ctx.senders, DISPLAY_SIZE * 2, "%llu.%llu.%llu",
+ ctx.transaction.data.tokenBurn.token.shardNum,
+ ctx.transaction.data.tokenBurn.token.realmNum,
+ ctx.transaction.data.tokenBurn.token.tokenNum);
+
+ hedera_snprintf(
+ ctx.amount, DISPLAY_SIZE * 2, "%s",
+ hedera_format_amount(ctx.transaction.data.tokenBurn.amount,
+ 0)); // always lowest denomination of token
+ } break;
+
+ case Hedera_TransactionBody_cryptoTransfer_tag: {
if ( // Only 1 Account (Sender), Fee 1 Tinybar, and Value 0 Tinybar
- ctx.transaction.data.cryptoTransfer.transfers.accountAmounts[0].amount == 0 &&
- ctx.transaction.data.cryptoTransfer.transfers.accountAmounts_count == 1 &&
- ctx.transaction.transactionFee == 1
- ) {
+ ctx.transaction.data.cryptoTransfer.transfers
+ .accountAmounts[ 0 ]
+ .amount == 0 &&
+ ctx.transaction.data.cryptoTransfer.transfers
+ .accountAmounts_count == 1 &&
+ ctx.transaction.transactionFee == 1) {
// Verify Account Transaction
ctx.type = Verify;
-
- hedera_sprintf(
- ctx.summary_line_1,
- "Verify Account"
- );
-
- hedera_sprintf(
- ctx.senders_title,
- "Account"
- );
-
- hedera_snprintf(
- ctx.senders,
- DISPLAY_SIZE * 2,
- "%llu.%llu.%llu",
- ctx.transaction.data.cryptoTransfer.transfers.accountAmounts[0].accountID.shardNum,
- ctx.transaction.data.cryptoTransfer.transfers.accountAmounts[0].accountID.realmNum,
- ctx.transaction.data.cryptoTransfer.transfers.accountAmounts[0].accountID.accountNum
- );
- hedera_snprintf(
- ctx.amount,
- DISPLAY_SIZE * 2,
- "%s hbar",
- hedera_format_tinybar(ctx.transaction.data.cryptoTransfer.transfers.accountAmounts[0].amount)
- );
- } else if (ctx.transaction.data.cryptoTransfer.transfers.accountAmounts_count == 2) {
+ hedera_sprintf(ctx.summary_line_1, "Verify Account");
+
+ hedera_sprintf(ctx.senders_title, "Account");
+
+ hedera_snprintf(ctx.senders, DISPLAY_SIZE * 2, "%llu.%llu.%llu",
+ ctx.transaction.data.cryptoTransfer.transfers
+ .accountAmounts[ 0 ]
+ .accountID.shardNum,
+ ctx.transaction.data.cryptoTransfer.transfers
+ .accountAmounts[ 0 ]
+ .accountID.realmNum,
+ ctx.transaction.data.cryptoTransfer.transfers
+ .accountAmounts[ 0 ]
+ .accountID.account);
+ } else if (ctx.transaction.data.cryptoTransfer.transfers
+ .accountAmounts_count == 2) {
// Number of Accounts == 2
- // Some other Transfer Transaction
+ // Hbar transfer between two accounts
ctx.type = Transfer;
- hedera_sprintf(
- ctx.summary_line_1,
- "Send Hbar"
- );
+ hedera_sprintf(ctx.summary_line_1, "Send Hbar");
- // Determine Sender based on amount
+ // Determine Sender based on transfers.accountAmounts
ctx.transfer_from_index = 0;
ctx.transfer_to_index = 1;
- if (ctx.transaction.data.cryptoTransfer.transfers.accountAmounts[0].amount > 0) {
+ if (ctx.transaction.data.cryptoTransfer.transfers
+ .accountAmounts[ 0 ]
+ .amount > 0) {
ctx.transfer_from_index = 1;
ctx.transfer_to_index = 0;
}
- hedera_snprintf(
- ctx.senders,
- DISPLAY_SIZE * 2,
- "%llu.%llu.%llu",
- ctx.transaction.data.cryptoTransfer.transfers.accountAmounts[ctx.transfer_from_index].accountID.shardNum,
- ctx.transaction.data.cryptoTransfer.transfers.accountAmounts[ctx.transfer_from_index].accountID.realmNum,
- ctx.transaction.data.cryptoTransfer.transfers.accountAmounts[ctx.transfer_from_index].accountID.accountNum
- );
+ hedera_snprintf(ctx.senders, DISPLAY_SIZE * 2, "%llu.%llu.%llu",
+ ctx.transaction.data.cryptoTransfer.transfers
+ .accountAmounts[ ctx.transfer_from_index ]
+ .accountID.shardNum,
+ ctx.transaction.data.cryptoTransfer.transfers
+ .accountAmounts[ ctx.transfer_from_index ]
+ .accountID.realmNum,
+ ctx.transaction.data.cryptoTransfer.transfers
+ .accountAmounts[ ctx.transfer_from_index ]
+ .accountID.account);
+
+ hedera_snprintf(ctx.recipients, DISPLAY_SIZE * 2,
+ "%llu.%llu.%llu",
+ ctx.transaction.data.cryptoTransfer.transfers
+ .accountAmounts[ ctx.transfer_to_index ]
+ .accountID.shardNum,
+ ctx.transaction.data.cryptoTransfer.transfers
+ .accountAmounts[ ctx.transfer_to_index ]
+ .accountID.realmNum,
+ ctx.transaction.data.cryptoTransfer.transfers
+ .accountAmounts[ ctx.transfer_to_index ]
+ .accountID.account);
hedera_snprintf(
- ctx.recipients,
- DISPLAY_SIZE * 2,
- "%llu.%llu.%llu",
- ctx.transaction.data.cryptoTransfer.transfers.accountAmounts[ctx.transfer_to_index].accountID.shardNum,
- ctx.transaction.data.cryptoTransfer.transfers.accountAmounts[ctx.transfer_to_index].accountID.realmNum,
- ctx.transaction.data.cryptoTransfer.transfers.accountAmounts[ctx.transfer_to_index].accountID.accountNum
- );
-
- hedera_snprintf(
- ctx.amount,
- DISPLAY_SIZE * 2,
- "%s hbar",
- hedera_format_tinybar(ctx.transaction.data.cryptoTransfer.transfers.accountAmounts[ctx.transfer_to_index].amount)
- );
- } else if ( ctx.transaction.data.cryptoTransfer.tokenTransfers_count == 1) {
+ ctx.amount, DISPLAY_SIZE * 2, "%s hbar",
+ hedera_format_tinybar(
+ ctx.transaction.data.cryptoTransfer.transfers
+ .accountAmounts[ ctx.transfer_to_index ]
+ .amount));
+ } else if (ctx.transaction.data.cryptoTransfer
+ .tokenTransfers_count == 1) {
+ // Fungible Token Transfer
ctx.type = TokenTransfer;
+ validate_token_transfer();
+
hedera_snprintf(
- ctx.summary_line_1,
- DISPLAY_SIZE * 2,
- "Send %llu.%llu.%llu",
- ctx.transaction.data.cryptoTransfer.tokenTransfers[0].token.shardNum,
- ctx.transaction.data.cryptoTransfer.tokenTransfers[0].token.realmNum,
- ctx.transaction.data.cryptoTransfer.tokenTransfers[0].token.tokenNum
- );
+ ctx.summary_line_1, DISPLAY_SIZE * 2, "Send %llu.%llu.%llu",
+ ctx.transaction.data.cryptoTransfer.tokenTransfers[ 0 ]
+ .token.shardNum,
+ ctx.transaction.data.cryptoTransfer.tokenTransfers[ 0 ]
+ .token.realmNum,
+ ctx.transaction.data.cryptoTransfer.tokenTransfers[ 0 ]
+ .token.tokenNum);
// Determine Sender based on amount
ctx.transfer_from_index = 0;
ctx.transfer_to_index = 1;
- if (ctx.transaction.data.cryptoTransfer.tokenTransfers[0].transfers[0].amount > 0)
- {
+ if (ctx.transaction.data.cryptoTransfer.tokenTransfers[ 0 ]
+ .transfers[ 0 ]
+ .amount > 0) {
ctx.transfer_from_index = 1;
ctx.transfer_to_index = 0;
}
hedera_snprintf(
- ctx.senders,
- DISPLAY_SIZE * 2,
- "%llu.%llu.%llu",
- ctx.transaction.data.cryptoTransfer.tokenTransfers[0].transfers[ctx.transfer_from_index].accountID.shardNum,
- ctx.transaction.data.cryptoTransfer.tokenTransfers[0].transfers[ctx.transfer_from_index].accountID.realmNum,
- ctx.transaction.data.cryptoTransfer.tokenTransfers[0].transfers[ctx.transfer_from_index].accountID.accountNum
- );
+ ctx.senders, DISPLAY_SIZE * 2, "%llu.%llu.%llu",
+ ctx.transaction.data.cryptoTransfer.tokenTransfers[ 0 ]
+ .transfers[ ctx.transfer_from_index ]
+ .accountID.shardNum,
+ ctx.transaction.data.cryptoTransfer.tokenTransfers[ 0 ]
+ .transfers[ ctx.transfer_from_index ]
+ .accountID.realmNum,
+ ctx.transaction.data.cryptoTransfer.tokenTransfers[ 0 ]
+ .transfers[ ctx.transfer_from_index ]
+ .accountID.account);
hedera_snprintf(
- ctx.recipients,
- DISPLAY_SIZE * 2,
- "%llu.%llu.%llu",
- ctx.transaction.data.cryptoTransfer.tokenTransfers[0].transfers[ctx.transfer_to_index].accountID.shardNum,
- ctx.transaction.data.cryptoTransfer.tokenTransfers[0].transfers[ctx.transfer_to_index].accountID.realmNum,
- ctx.transaction.data.cryptoTransfer.tokenTransfers[0].transfers[ctx.transfer_to_index].accountID.accountNum
- );
-
- validate_decimals(ctx.transaction.data.cryptoTransfer.tokenTransfers[0].expected_decimals.value);
+ ctx.recipients, DISPLAY_SIZE * 2, "%llu.%llu.%llu",
+ ctx.transaction.data.cryptoTransfer.tokenTransfers[ 0 ]
+ .transfers[ ctx.transfer_to_index ]
+ .accountID.shardNum,
+ ctx.transaction.data.cryptoTransfer.tokenTransfers[ 0 ]
+ .transfers[ ctx.transfer_to_index ]
+ .accountID.realmNum,
+ ctx.transaction.data.cryptoTransfer.tokenTransfers[ 0 ]
+ .transfers[ ctx.transfer_to_index ]
+ .accountID.account);
+
hedera_snprintf(
- ctx.amount,
- DISPLAY_SIZE * 2,
- "%s",
+ ctx.amount, DISPLAY_SIZE * 2, "%s",
hedera_format_amount(
- ctx.transaction.data.cryptoTransfer.tokenTransfers[0].transfers[ctx.transfer_to_index].amount,
- ctx.transaction.data.cryptoTransfer.tokenTransfers[0].expected_decimals.value
- )
- );
+ ctx.transaction.data.cryptoTransfer.tokenTransfers[ 0 ]
+ .transfers[ ctx.transfer_to_index ]
+ .amount,
+ ctx.transaction.data.cryptoTransfer.tokenTransfers[ 0 ]
+ .expected_decimals.value));
} else {
// Unsupported
THROW(EXCEPTION_MALFORMED_APDU);
@@ -1281,13 +1427,23 @@ void handle_transaction_body() {
}
switch (ctx.type) {
- case Associate:
case Verify:
ux_flow_init(0, ux_verify_flow, NULL);
break;
+ case TokenMint:
+ case TokenBurn:
+ ux_flow_init(0, ux_mint_flow, NULL);
+ break;
case Create:
ux_flow_init(0, ux_create_flow, NULL);
break;
+ case Update:
+ ux_flow_init(0, ux_update_flow, NULL);
+ break;
+ case Associate:
+ case Dissociate:
+ ux_flow_init(0, ux_associate_flow, NULL);
+ break;
case TokenTransfer:
case Transfer:
ux_flow_init(0, ux_transfer_flow, NULL);
@@ -1301,14 +1457,10 @@ void handle_transaction_body() {
// Sign Handler
// Decodes and handles transaction message
-void handle_sign_transaction(
- uint8_t p1,
- uint8_t p2,
- uint8_t* buffer,
- uint16_t len,
- /* out */ volatile unsigned int* flags,
- /* out */ volatile unsigned int* tx
-) {
+void handle_sign_transaction(uint8_t p1, uint8_t p2, uint8_t* buffer,
+ uint16_t len,
+ /* out */ volatile unsigned int* flags,
+ /* out */ volatile unsigned int* tx) {
UNUSED(p1);
UNUSED(p2);
UNUSED(tx);
@@ -1317,7 +1469,7 @@ void handle_sign_transaction(
ctx.key_index = U4LE(buffer, 0);
// Raw Tx
- uint8_t raw_transaction[MAX_TX_SIZE];
+ uint8_t raw_transaction[ MAX_TX_SIZE ];
int raw_transaction_length = len - 4;
// Oops Oof Owie
@@ -1330,27 +1482,17 @@ void handle_sign_transaction(
// Sign Transaction
// TODO: handle error return here (internal error?!)
- if (!hedera_sign(
- ctx.key_index,
- raw_transaction,
- raw_transaction_length,
- G_io_apdu_buffer
- )) {
+ if (!hedera_sign(ctx.key_index, raw_transaction, raw_transaction_length,
+ G_io_apdu_buffer)) {
THROW(EXCEPTION_INTERNAL);
}
// Make in memory buffer into stream
- pb_istream_t stream = pb_istream_from_buffer(
- raw_transaction,
- raw_transaction_length
- );
+ pb_istream_t stream =
+ pb_istream_from_buffer(raw_transaction, raw_transaction_length);
// Decode the Transaction
- if (!pb_decode(
- &stream,
- HederaTransactionBody_fields,
- &ctx.transaction
- )) {
+ if (!pb_decode(&stream, Hedera_TransactionBody_fields, &ctx.transaction)) {
// Oh no couldn't ...
THROW(EXCEPTION_MALFORMED_APDU);
}
@@ -1360,44 +1502,21 @@ void handle_sign_transaction(
*flags |= IO_ASYNCH_REPLY;
}
-// Validates whether or not a transfer is legal:
-// Either a transfer between two accounts
-// Or a token transfer between two accounts
-void validate_transfer() {
- if (ctx.transaction.data.cryptoTransfer.transfers.accountAmounts_count > 2) {
- // More than two accounts in a transfer
- THROW(EXCEPTION_MALFORMED_APDU);
- }
-
- if (
- ctx.transaction.data.cryptoTransfer.transfers.accountAmounts_count == 2 &&
- ctx.transaction.data.cryptoTransfer.tokenTransfers_count != 0
- ) {
- // Can't also transfer tokens while sending hbar
- THROW(EXCEPTION_MALFORMED_APDU);
- }
-
- if (ctx.transaction.data.cryptoTransfer.tokenTransfers_count > 1) {
- // More than one token transferred
+void validate_decimals(uint32_t decimals) {
+ if (decimals >= 20) {
+ // We only support decimal values less than 20
THROW(EXCEPTION_MALFORMED_APDU);
}
-
- if (ctx.transaction.data.cryptoTransfer.tokenTransfers_count == 1) {
- if (ctx.transaction.data.cryptoTransfer.tokenTransfers[0].transfers_count != 2) {
- // More than two accounts in a token transfer
- THROW(EXCEPTION_MALFORMED_APDU);
- }
-
- if (ctx.transaction.data.cryptoTransfer.transfers.accountAmounts_count != 0) {
- // Can't also transfer Hbar if the transaction is an otherwise valid token transfer
- THROW(EXCEPTION_MALFORMED_APDU);
- }
- }
}
-void validate_decimals(uint32_t decimals) {
- if (decimals >= 20) {
- // We only support decimal values less than 20
+void validate_token_transfer() {
+ // One token transfer with two accountAmounts
+ if (ctx.transaction.data.cryptoTransfer.tokenTransfers[ 0 ]
+ .transfers_count != 2) {
THROW(EXCEPTION_MALFORMED_APDU);
}
+
+ // Transactions fail if not given in the right denomination
+ validate_decimals(ctx.transaction.data.cryptoTransfer.tokenTransfers[ 0 ]
+ .expected_decimals.value);
}
diff --git a/src/sign_transaction.h b/src/sign_transaction.h
index 8496d1dc..3cb980db 100644
--- a/src/sign_transaction.h
+++ b/src/sign_transaction.h
@@ -1,6 +1,25 @@
#ifndef LEDGER_APP_HEDERA_SIGN_TRANSACTION_H
#define LEDGER_APP_HEDERA_SIGN_TRANSACTION_H 1
+#include
+#include
+#include
+#include
+#include
+
+#include "basic_types.pb.h"
+#include "debug.h"
+#include "errors.h"
+#include "globals.h"
+#include "handlers.h"
+#include "hedera.h"
+#include "hedera_format.h"
+#include "io.h"
+#include "printf.h"
+#include "transaction_body.pb.h"
+#include "ui.h"
+#include "utils.h"
+
enum TransactionStep {
Summary = 1,
Operator = 2,
@@ -17,68 +36,129 @@ enum TransactionType {
Unknown = -1,
Verify = 0,
Create = 1,
- Transfer = 2,
- Associate = 3,
- TokenTransfer = 4,
- TokenMint = 5,
- TokenBurn = 6,
+ Update = 2,
+ Transfer = 3,
+ Associate = 4,
+ Dissociate = 5,
+ TokenTransfer = 6,
+ TokenMint = 7,
+ TokenBurn = 8,
};
+void handle_transaction_body();
+void validate_decimals(uint32_t decimals);
+void validate_token_transfer();
+
#if defined(TARGET_NANOS)
+// Transaction Context
+static struct sign_tx_context_t {
+ // ui common
+ uint32_t key_index;
+ uint8_t transfer_to_index;
+ uint8_t transfer_from_index;
+
+ // Transaction Summary
+ char summary_line_1[ DISPLAY_SIZE + 1 ];
+ char summary_line_2[ DISPLAY_SIZE + 1 ];
+ char title[ DISPLAY_SIZE + 1 ];
+
+ // Account ID: uint64_t.uint64_t.uint64_t
+ // Most other entities are shorter
+ char full[ ACCOUNT_ID_SIZE + 1 ];
+ char partial[ DISPLAY_SIZE + 1 ];
+
+ // Steps correspond to parts of the transaction proto
+ // type is set based on proto
+ enum TransactionStep step;
+ enum TransactionType type;
+
+ uint8_t current_page; // 1 -> Number Screens
+ uint8_t page_count; // Number Screens
+
+ // Parsed transaction
+ Hedera_TransactionBody transaction;
+} ctx;
+
// Forward declarations for Nano S UI
// Step 1
-unsigned int ui_tx_summary_step_button(
- unsigned int button_mask,
- unsigned int button_mask_counter
-);
+unsigned int ui_tx_summary_step_button(unsigned int button_mask,
+ unsigned int button_mask_counter);
// Step 2 - 7
void handle_intermediate_left_press();
void handle_intermediate_right_press();
-unsigned int ui_tx_intermediate_step_button(
- unsigned int button_mask,
- unsigned int button_mask_counter
-);
+unsigned int ui_tx_intermediate_step_button(unsigned int button_mask,
+ unsigned int button_mask_counter);
// Step 8
-unsigned int ui_tx_confirm_step_button(
- unsigned int button_mask,
- unsigned int button_mask_counter
-);
+unsigned int ui_tx_confirm_step_button(unsigned int button_mask,
+ unsigned int button_mask_counter);
// Step 9
-unsigned int ui_tx_deny_step_button(
- unsigned int button_mask,
- unsigned int button_mask_counter
-);
+unsigned int ui_tx_deny_step_button(unsigned int button_mask,
+ unsigned int button_mask_counter);
uint8_t num_screens(size_t length);
-void count_screens();
-void shift_display();
-bool first_screen();
-bool last_screen();
-
-#elif defined(TARGET_NANOX) || defined(TARGET_NANOS2)
-// Forward declarations for Nano X UI
-void x_start_tx_loop();
-void x_continue_tx_loop();
-void x_end_tx_loop();
-unsigned int io_seproxyhal_tx_approve(const bagl_element_t* e);
-unsigned int io_seproxyhal_tx_reject(const bagl_element_t* e);
-
-#endif // TARGET
-
+void count_screens_of_step();
+bool first_screen_of_step();
+bool last_screen_of_step();
void reformat_token();
-void reformat_tokens_accounts(char *title_part, uint8_t transfer_index);
-void reformat_accounts(char *title_part, uint8_t transfer_index);
+void reformat_tokens_accounts(char* title_part, uint8_t transfer_index);
+void reformat_accounts(char* title_part, uint8_t transfer_index);
+void reformat_stake_target();
+void reformat_collect_rewards();
+void reformat_target_account();
void reformat_operator();
void reformat_senders();
void reformat_recipients();
void reformat_amount();
void reformat_fee();
void reformat_memo();
-void handle_transaction_body();
-void validate_transfer();
-void validate_decimals(uint32_t decimals);
-#endif //LEDGER_APP_HEDERA_SIGN_TRANSACTION_H
+#elif defined(TARGET_NANOX) || defined(TARGET_NANOS2)
+// Transaction Context
+static struct sign_tx_context_t {
+ // ui common
+ uint32_t key_index;
+ uint8_t transfer_from_index;
+ uint8_t transfer_to_index;
+
+ // Transaction Summary
+ char summary_line_1[ DISPLAY_SIZE + 1 ];
+ char summary_line_2[ DISPLAY_SIZE + 1 ];
+ char senders_title[ DISPLAY_SIZE + 1 ];
+ char recipients_title[ DISPLAY_SIZE + 1 ];
+ char amount_title[ DISPLAY_SIZE + 1 ];
+ char partial[ DISPLAY_SIZE + 1 ];
+
+ enum TransactionType type;
+
+ // Transaction Operator
+ char operator[ DISPLAY_SIZE * 2 + 1 ];
+
+ // Transaction Senders
+ char senders[ DISPLAY_SIZE * 2 + 1 ];
+
+ // Transaction Recipients
+ char recipients[ DISPLAY_SIZE * 2 + 1 ];
+
+ // Transaction Amount
+ char amount[ DISPLAY_SIZE * 2 + 1 ];
+
+ // Transaction Fee
+ char fee[ DISPLAY_SIZE * 2 + 1 ];
+
+ // Transaction Memo
+ char memo[ MAX_MEMO_SIZE + 1 ];
+
+ // Parsed transaction
+ Hedera_TransactionBody transaction;
+} ctx;
+
+// Forward declarations for Nano X UI
+unsigned int io_seproxyhal_tx_approve(const bagl_element_t* e);
+unsigned int io_seproxyhal_tx_reject(const bagl_element_t* e);
+
+#endif // TARGET
+
+#endif // LEDGER_APP_HEDERA_SIGN_TRANSACTION_H
diff --git a/src/ui.c b/src/ui.c
index 09cf020e..7766091e 100644
--- a/src/ui.c
+++ b/src/ui.c
@@ -9,108 +9,81 @@ ux_state_t ux;
unsigned int ux_step;
unsigned int ux_step_count;
-static const ux_menu_entry_t menu_main[4];
-
-static const ux_menu_entry_t menu_about[3] = {
- {
- .menu = NULL,
- .callback = NULL,
- .userid = 0,
- .icon = NULL,
- .line1 = "Version",
- .line2 = APPVERSION,
- .text_x = 0,
- .icon_x = 0,
- },
-
- {
- .menu = menu_main,
- .callback = NULL,
- .userid = 0,
- .icon = &C_icon_back,
- .line1 = "Back",
- .line2 = NULL,
- .text_x = 61,
- .icon_x = 40,
- },
-
- UX_MENU_END
-};
-
-static const ux_menu_entry_t menu_main[4] = {
- {
- .menu = NULL,
- .callback = NULL,
- .userid = 0,
- .icon = NULL,
- .line1 = "Awaiting",
- .line2 = "Commands",
- .text_x = 0,
- .icon_x = 0
- },
- {
- .menu = menu_about,
- .callback = NULL,
- .userid = 0,
- .icon = NULL,
- .line1 = "About",
- .line2 = NULL,
- .text_x = 0,
- .icon_x = 0,
- },
-
- {
- .menu = NULL,
- .callback = &os_sched_exit,
- .userid = 0,
- .icon = &C_icon_dashboard,
- .line1 = "Quit app",
- .line2 = NULL,
- .text_x = 50,
- .icon_x = 29,
- },
-
- UX_MENU_END
-};
+static const ux_menu_entry_t menu_main[ 4 ];
+
+static const ux_menu_entry_t menu_about[ 3 ] = {{
+ .menu = NULL,
+ .callback = NULL,
+ .userid = 0,
+ .icon = NULL,
+ .line1 = "Version",
+ .line2 = APPVERSION,
+ .text_x = 0,
+ .icon_x = 0,
+ },
+
+ {
+ .menu = menu_main,
+ .callback = NULL,
+ .userid = 0,
+ .icon = &C_icon_back,
+ .line1 = "Back",
+ .line2 = NULL,
+ .text_x = 61,
+ .icon_x = 40,
+ },
+
+ UX_MENU_END};
+
+static const ux_menu_entry_t menu_main[ 4 ] = {{.menu = NULL,
+ .callback = NULL,
+ .userid = 0,
+ .icon = NULL,
+ .line1 = "Awaiting",
+ .line2 = "Commands",
+ .text_x = 0,
+ .icon_x = 0},
+ {
+ .menu = menu_about,
+ .callback = NULL,
+ .userid = 0,
+ .icon = NULL,
+ .line1 = "About",
+ .line2 = NULL,
+ .text_x = 0,
+ .icon_x = 0,
+ },
+
+ {
+ .menu = NULL,
+ .callback = &os_sched_exit,
+ .userid = 0,
+ .icon = &C_icon_dashboard,
+ .line1 = "Quit app",
+ .line2 = NULL,
+ .text_x = 50,
+ .icon_x = 29,
+ },
+
+ UX_MENU_END};
#elif defined(TARGET_NANOX) || defined(TARGET_NANOS2)
ux_state_t G_ux;
bolos_ux_params_t G_ux_params;
-UX_STEP_NOCB(
- ux_idle_flow_1_step,
- nn,
- {
- "Awaiting",
- "Commands"
- }
-);
-
-UX_STEP_NOCB(
- ux_idle_flow_2_step,
- bn,
- {
- "Version",
- APPVERSION,
- }
-);
-
-UX_STEP_VALID(
- ux_idle_flow_3_step,
- pb,
- os_sched_exit(-1),
- {
- &C_icon_dashboard_x,
- "Exit"
- }
-);
-
-UX_DEF(
- ux_idle_flow,
- &ux_idle_flow_1_step,
- &ux_idle_flow_2_step,
- &ux_idle_flow_3_step
-);
+UX_STEP_NOCB(ux_idle_flow_1_step, nn, {"Awaiting", "Commands"});
+
+UX_STEP_NOCB(ux_idle_flow_2_step, bn,
+ {
+ "Version",
+ APPVERSION,
+ });
+
+UX_STEP_VALID(ux_idle_flow_3_step, pb, os_sched_exit(-1),
+ {&C_icon_dashboard_x, "Exit"});
+
+UX_DEF(ux_idle_flow, &ux_idle_flow_1_step, &ux_idle_flow_2_step,
+ &ux_idle_flow_3_step);
#endif // TARGET
diff --git a/src/ui.h b/src/ui.h
index dce88375..087a8309 100644
--- a/src/ui.h
+++ b/src/ui.h
@@ -1,8 +1,8 @@
#ifndef LEDGER_HEDERA_UI_H
#define LEDGER_HEDERA_UI_H 1
-#include "glyphs.h"
#include "globals.h"
+#include "glyphs.h"
#include "ux.h"
#if defined(TARGET_NANOS)
@@ -10,11 +10,51 @@
#include
// Common UI element definitions for Nano S
-#define UI_BACKGROUND() {{BAGL_RECTANGLE,0,0,0,128,32,0,0,BAGL_FILL,0,0xFFFFFF,0,0},NULL}
-#define UI_ICON_LEFT(userid, glyph) {{BAGL_ICON,userid,3,12,7,7,0,0,0,0xFFFFFF,0,0,glyph},NULL}
-#define UI_ICON_RIGHT(userid, glyph) {{BAGL_ICON,userid,117,13,8,6,0,0,0,0xFFFFFF,0,0,glyph},NULL}
-#define UI_TEXT(userid, x, y, w, text) {{BAGL_LABELINE,userid,x,y,w,12,0,0,0,0xFFFFFF,0,BAGL_FONT_OPEN_SANS_REGULAR_11px|BAGL_FONT_ALIGNMENT_CENTER,0},(char *)(text)}
-#define UI_ICON(userid, x, y, w, glyph) {{BAGL_ICON,userid,x,y,w,6,0,0,0,0xFFFFFF,0,BAGL_FONT_OPEN_SANS_REGULAR_11px|BAGL_FONT_ALIGNMENT_CENTER,glyph},NULL}
+#define UI_BACKGROUND() \
+ { \
+ { \
+ BAGL_RECTANGLE, 0, 0, 0, 128, 32, 0, 0, \
+ BAGL_FILL, 0, 0xFFFFFF, 0, 0}, \
+ NULL \
+ }
+#define UI_ICON_LEFT(userid, glyph) \
+ { {BAGL_ICON, userid, 3, 12, 7, 7, 0, 0, 0, 0xFFFFFF, 0, 0, glyph}, NULL }
+#define UI_ICON_RIGHT(userid, glyph) \
+ { {BAGL_ICON, userid, 117, 13, 8, 6, 0, 0, 0, 0xFFFFFF, 0, 0, glyph}, NULL }
+#define UI_TEXT(userid, x, y, w, text) \
+ { \
+ {BAGL_LABELINE, \
+ userid, \
+ x, \
+ y, \
+ w, \
+ 12, \
+ 0, \
+ 0, \
+ 0, \
+ 0xFFFFFF, \
+ 0, \
+ BAGL_FONT_OPEN_SANS_REGULAR_11px | BAGL_FONT_ALIGNMENT_CENTER, \
+ 0}, \
+ (char*)(text) \
+ }
+#define UI_ICON(userid, x, y, w, glyph) \
+ { \
+ {BAGL_ICON, \
+ userid, \
+ x, \
+ y, \
+ w, \
+ 6, \
+ 0, \
+ 0, \
+ 0, \
+ 0xFFFFFF, \
+ 0, \
+ BAGL_FONT_OPEN_SANS_REGULAR_11px | BAGL_FONT_ALIGNMENT_CENTER, \
+ glyph}, \
+ NULL \
+ }
#endif // TARGET
diff --git a/src/utils.c b/src/utils.c
index 1f4adf32..aab33333 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -1,20 +1,20 @@
#include "utils.h"
-void public_key_to_bytes(unsigned char *dst, cx_ecfp_public_key_t *public) {
+void public_key_to_bytes(unsigned char* dst, cx_ecfp_public_key_t* public) {
for (int i = 0; i < 32; i++) {
- dst[i] = public->W[64 - i];
+ dst[ i ] = public->W[ 64 - i ];
}
- if (public->W[32] & 1) {
- dst[31] |= 0x80;
+ if (public->W[ 32 ] & 1) {
+ dst[ 31 ] |= 0x80;
}
}
-void bin2hex(uint8_t *dst, uint8_t *data, uint64_t inlen) {
- static uint8_t const hex[] = "0123456789abcdef";
- for (uint64_t i = 0; i < inlen; i++) {
- dst[2*i+0] = hex[(data[i]>>4) & 0x0F];
- dst[2*i+1] = hex[(data[i]>>0) & 0x0F];
- }
- dst[2*inlen] = '\0';
+void bin2hex(uint8_t* dst, uint8_t* data, uint64_t inlen) {
+ static uint8_t const hex[] = "0123456789abcdef";
+ for (uint64_t i = 0; i < inlen; i++) {
+ dst[ 2 * i + 0 ] = hex[ (data[ i ] >> 4) & 0x0F ];
+ dst[ 2 * i + 1 ] = hex[ (data[ i ] >> 0) & 0x0F ];
+ }
+ dst[ 2 * inlen ] = '\0';
}
diff --git a/src/utils.h b/src/utils.h
index 29b00d5b..f080acb0 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -1,10 +1,10 @@
#ifndef LEDGER_HEDERA_UTILS_H
#define LEDGER_HEDERA_UTILS_H 1
-#include
#include
+#include
-extern void public_key_to_bytes(uint8_t *dst, cx_ecfp_public_key_t *public);
-extern void bin2hex(uint8_t *dst, uint8_t *data, uint64_t inlen);
+extern void public_key_to_bytes(uint8_t* dst, cx_ecfp_public_key_t* public);
+extern void bin2hex(uint8_t* dst, uint8_t* data, uint64_t inlen);
#endif // LEDGER_HEDERA_UTILS_H
diff --git a/tests/.pdm.toml b/tests/.pdm.toml
new file mode 100644
index 00000000..04b4f314
--- /dev/null
+++ b/tests/.pdm.toml
@@ -0,0 +1,2 @@
+[python]
+path = "/home/chris/Projects/Ledger/ledger-app-hedera/tests/.venv/bin/python"
diff --git a/tests/.vscode/settings.json b/tests/.vscode/settings.json
new file mode 100644
index 00000000..48e647b5
--- /dev/null
+++ b/tests/.vscode/settings.json
@@ -0,0 +1,4 @@
+{
+ "python.formatting.provider": "black",
+ "python.analysis.typeCheckingMode": "basic"
+}
\ No newline at end of file
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/apps/__init__.py b/tests/apps/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/apps/hedera.py b/tests/apps/hedera.py
new file mode 100644
index 00000000..88bcb89f
--- /dev/null
+++ b/tests/apps/hedera.py
@@ -0,0 +1,91 @@
+from typing import List, Generator, Dict
+from enum import IntEnum
+from contextlib import contextmanager
+from time import sleep
+
+from ragger.backend.interface import BackendInterface, RAPDU
+
+from ..utils import validate_displayed_message
+from .hedera_builder import hedera_transaction
+
+
+class INS(IntEnum):
+ INS_GET_APP_CONFIGURATION = 0x01
+ INS_GET_PUBLIC_KEY = 0x02
+ INS_SIGN_TRANSACTION = 0x04
+
+CLA = 0xE0
+
+P1_CONFIRM = 0x00
+P1_NON_CONFIRM = 0x01
+
+P2_EXTEND = 0x01
+P2_MORE = 0x02
+
+
+PUBLIC_KEY_LENGTH = 32
+
+MAX_CHUNK_SIZE = 255
+
+
+STATUS_OK = 0x9000
+
+class ErrorType:
+ EXCEPTION_USER_REJECTED = 0x6985
+
+
+def to_zigzag(n):
+ return n + n + (n < 0)
+
+
+class HederaClient:
+ client: BackendInterface
+
+ def __init__(self, client):
+ self._client = client
+
+ def get_public_key_non_confirm(self, index: int) -> RAPDU:
+ index_b = index.to_bytes(4, "little")
+ return self._client.exchange(CLA, INS.INS_GET_PUBLIC_KEY, P1_NON_CONFIRM, 0, index_b)
+
+ @contextmanager
+ def get_public_key_confirm(self, index: int) -> Generator[None, None, None]:
+ index_b = index.to_bytes(4, "little")
+ with self._client.exchange_async(CLA, INS.INS_GET_PUBLIC_KEY, P1_CONFIRM, 0, index_b):
+ sleep(0.5)
+ yield
+
+ def validate(self):
+ self._client.right_click()
+
+ def refuse(self):
+ self._client.left_click()
+
+ def validate_screen(self, right_clicks: int):
+ validate_displayed_message(self._client, right_clicks)
+
+ def get_async_response(self) -> RAPDU:
+ return self._client.last_async_response
+
+ @contextmanager
+ def send_sign_transaction(self,
+ index: int,
+ operator_shard_num: int,
+ operator_realm_num: int,
+ operator_account_num: int,
+ transaction_fee: int,
+ memo: str,
+ conf: Dict) -> Generator[None, None, None]:
+
+ transaction = hedera_transaction(operator_shard_num,
+ operator_realm_num,
+ operator_account_num,
+ transaction_fee,
+ memo,
+ conf)
+
+ payload = index.to_bytes(4, "little") + transaction
+
+ with self._client.exchange_async(CLA, INS.INS_SIGN_TRANSACTION, P1_CONFIRM, 0, payload):
+ sleep(0.5)
+ yield
diff --git a/tests/apps/hedera_builder.py b/tests/apps/hedera_builder.py
new file mode 100644
index 00000000..3f5da7df
--- /dev/null
+++ b/tests/apps/hedera_builder.py
@@ -0,0 +1,319 @@
+from typing import Dict
+
+from proto import basic_types_pb2 as basics
+from proto import crypto_create_pb2 as create
+from proto import crypto_update_pb2 as update
+from proto import crypto_transfer_pb2 as transfer
+from proto import token_associate_pb2 as associate
+from proto import token_dissociate_pb2 as dissociate
+from proto import token_burn_pb2 as burn
+from proto import token_mint_pb2 as mint
+from proto import transaction_body_pb2 as tx_body
+from proto import wrappers_pb2 as wrappers
+
+
+def hedera_transaction(
+ operator_shard_num: int,
+ operator_realm_num: int,
+ operator_account_num: int,
+ transaction_fee: int,
+ memo: str,
+ conf: Dict,
+) -> bytes:
+ operator = basics.AccountID(
+ shardNum=operator_shard_num,
+ realmNum=operator_realm_num,
+ accountNum=operator_account_num,
+ )
+
+ hedera_transaction_id = basics.TransactionID(
+ accountID=operator,
+ )
+
+ transaction = tx_body.TransactionBody(
+ transactionID=hedera_transaction_id,
+ transactionFee=transaction_fee,
+ memo=memo,
+ **conf,
+ )
+
+ return transaction.SerializeToString()
+
+
+def crypto_create_account_conf(initialBalance: int) -> Dict:
+ crypto_create_account = create.CryptoCreateTransactionBody(
+ initialBalance=initialBalance,
+ )
+
+ return {"cryptoCreateAccount": crypto_create_account}
+
+
+def crypto_create_account_stake_account_conf() -> Dict:
+ stake_target = basics.AccountID(shardNum=1, realmNum=2, accountNum=3)
+
+ crypto_create_account = create.CryptoCreateTransactionBody(
+ initialBalance=5, staked_account_id=stake_target
+ )
+
+ return {"cryptoCreateAccount": crypto_create_account}
+
+
+def crypto_create_account_stake_node_conf() -> Dict:
+ stake_target = 3
+
+ crypto_create_account = create.CryptoCreateTransactionBody(
+ initialBalance=5, staked_node_id=stake_target
+ )
+
+ return {"cryptoCreateAccount": crypto_create_account}
+
+
+def crypto_create_account_stake_toggle_rewards_conf() -> Dict:
+ stake_target = basics.AccountID(shardNum=6, realmNum=6, accountNum=6)
+
+ crypto_create_account = create.CryptoCreateTransactionBody(
+ initialBalance=5, staked_account_id=stake_target, decline_reward=True
+ )
+
+ return {"cryptoCreateAccount": crypto_create_account}
+
+
+def account_update_conf() -> Dict:
+ stake_target = basics.AccountID(shardNum=6, realmNum=6, accountNum=6)
+
+ crypto_update_account = update.CryptoUpdateTransactionBody(
+ staked_account_id=stake_target
+ )
+
+ return {"cryptoUpdateAccount": crypto_update_account}
+
+
+def crypto_transfer_token_conf(
+ token_shardNum: int,
+ token_realmNum: int,
+ token_tokenNum: int,
+ sender_shardNum: int,
+ sender_realmNum: int,
+ sender_accountNum: int,
+ recipient_shardNum: int,
+ recipient_realmNum: int,
+ recipient_accountNum: int,
+ amount: int,
+ decimals: int,
+) -> Dict:
+
+ hedera_token_id = basics.TokenID(
+ shardNum=token_shardNum,
+ realmNum=token_realmNum,
+ tokenNum=token_tokenNum,
+ )
+
+ hedera_account_id_sender = basics.AccountID(
+ shardNum=sender_shardNum,
+ realmNum=sender_realmNum,
+ accountNum=sender_accountNum,
+ )
+
+ hedera_account_amount_sender = basics.AccountAmount(
+ accountID=hedera_account_id_sender,
+ amount=0,
+ )
+
+ hedera_account_id_recipient = basics.AccountID(
+ shardNum=recipient_shardNum,
+ realmNum=recipient_realmNum,
+ accountNum=recipient_accountNum,
+ )
+
+ hedera_account_amount_recipient = basics.AccountAmount(
+ accountID=hedera_account_id_recipient,
+ amount=amount,
+ )
+
+ hedera_transfer_list = basics.TransferList(
+ accountAmounts=[],
+ )
+
+ decimalsUInt32 = wrappers.UInt32Value(
+ value=decimals,
+ )
+
+ hedera_token_transfer_list = basics.TokenTransferList(
+ token=hedera_token_id,
+ transfers=[hedera_account_amount_recipient, hedera_account_amount_sender],
+ expected_decimals=decimalsUInt32,
+ )
+
+ crypto_transfer = transfer.CryptoTransferTransactionBody(
+ transfers=hedera_transfer_list,
+ tokenTransfers=[hedera_token_transfer_list],
+ )
+
+ return {"cryptoTransfer": crypto_transfer}
+
+
+def crypto_transfer_hbar_conf(
+ sender_shardNum: int,
+ sender_realmNum: int,
+ sender_accountNum: int,
+ recipient_shardNum: int,
+ recipient_realmNum: int,
+ recipient_accountNum: int,
+ amount: int,
+) -> Dict:
+
+ hedera_account_id_sender = basics.AccountID(
+ shardNum=sender_shardNum,
+ realmNum=sender_realmNum,
+ accountNum=sender_accountNum,
+ )
+
+ hedera_account_amount_sender = basics.AccountAmount(
+ accountID=hedera_account_id_sender,
+ amount=0,
+ )
+
+ hedera_account_id_recipient = basics.AccountID(
+ shardNum=recipient_shardNum,
+ realmNum=recipient_realmNum,
+ accountNum=recipient_accountNum,
+ )
+
+ hedera_account_amount_recipient = basics.AccountAmount(
+ accountID=hedera_account_id_recipient,
+ amount=amount,
+ )
+
+ hedera_transfer_list = basics.TransferList(
+ accountAmounts=[hedera_account_amount_recipient, hedera_account_amount_sender],
+ )
+
+ crypto_transfer = transfer.CryptoTransferTransactionBody(
+ transfers=hedera_transfer_list,
+ tokenTransfers=[],
+ )
+
+ return {"cryptoTransfer": crypto_transfer}
+
+
+def crypto_transfer_verify(
+ sender_shardNum: int, sender_realmNum: int, sender_accountNum: int
+) -> Dict:
+
+ hedera_account_id_sender = basics.AccountID(
+ shardNum=sender_shardNum,
+ realmNum=sender_realmNum,
+ accountNum=sender_accountNum,
+ )
+
+ hedera_account_amount_sender = basics.AccountAmount(
+ accountID=hedera_account_id_sender,
+ amount=0,
+ )
+
+ hedera_transfer_list = basics.TransferList(
+ accountAmounts=[hedera_account_amount_sender],
+ )
+
+ crypto_transfer = transfer.CryptoTransferTransactionBody(
+ transfers=hedera_transfer_list,
+ tokenTransfers=[],
+ )
+
+ return {"cryptoTransfer": crypto_transfer}
+
+
+def token_associate_conf(
+ token_shardNum: int,
+ token_realmNum: int,
+ token_tokenNum: int,
+ sender_shardNum: int,
+ sender_realmNum: int,
+ sender_accountNum: int,
+) -> Dict:
+
+ hedera_account_id_sender = basics.AccountID(
+ shardNum=sender_shardNum,
+ realmNum=sender_realmNum,
+ accountNum=sender_accountNum,
+ )
+
+ hedera_token_id = basics.TokenID(
+ shardNum=token_shardNum,
+ realmNum=token_realmNum,
+ tokenNum=token_tokenNum,
+ )
+
+ token_associate = associate.TokenAssociateTransactionBody(
+ account=hedera_account_id_sender,
+ tokens=[hedera_token_id],
+ )
+
+ return {"tokenAssociate": token_associate}
+
+
+def token_dissociate_conf(
+ token_shardNum: int,
+ token_realmNum: int,
+ token_tokenNum: int,
+ sender_shardNum: int,
+ sender_realmNum: int,
+ sender_accountNum: int,
+) -> Dict:
+
+ hedera_account_id_sender = basics.AccountID(
+ shardNum=sender_shardNum,
+ realmNum=sender_realmNum,
+ accountNum=sender_accountNum,
+ )
+
+ hedera_token_id = basics.TokenID(
+ shardNum=token_shardNum,
+ realmNum=token_realmNum,
+ tokenNum=token_tokenNum,
+ )
+
+ token_dissociate = dissociate.TokenDissociateTransactionBody(
+ account=hedera_account_id_sender,
+ tokens=[hedera_token_id],
+ )
+
+ return {"tokenDissociate": token_dissociate}
+
+
+def token_burn_conf(
+ token_shardNum: int,
+ token_realmNum: int,
+ token_tokenNum: int,
+ amount: int,
+) -> Dict:
+ hedera_token_id = basics.TokenID(
+ shardNum=token_shardNum,
+ realmNum=token_realmNum,
+ tokenNum=token_tokenNum,
+ )
+
+ token_burn = burn.TokenBurnTransactionBody(token=hedera_token_id, amount=amount)
+
+ return {"tokenBurn": token_burn}
+
+
+def token_mint_conf(
+ token_shardNum: int,
+ token_realmNum: int,
+ token_tokenNum: int,
+ amount: int,
+) -> Dict:
+
+ hedera_token_id = basics.TokenID(
+ shardNum=token_shardNum,
+ realmNum=token_realmNum,
+ tokenNum=token_tokenNum,
+ )
+
+ token_mint = mint.TokenMintTransactionBody(
+ token=hedera_token_id,
+ amount=amount,
+ )
+
+ return {"tokenMint": token_mint}
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 00000000..522666e0
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,99 @@
+from pathlib import Path
+import pytest
+
+from ragger import Firmware
+from ragger.backend import SpeculosBackend, LedgerCommBackend, LedgerWalletBackend
+
+from .utils import app_path_from_app_name
+
+def __str__(self): # also tried __repr__()
+ # Attempt to print the 'select' attribute in "pytest -v" output
+ return self.select
+
+
+APPS_DIRECTORY = (Path(__file__).parent / "elfs").resolve()
+
+APP_NAME = "hedera"
+
+BACKENDS = ["speculos", "ledgercomm", "ledgerwallet"]
+
+FIRMWARES = [
+ Firmware('nanos', '2.1'),
+ Firmware('nanox', '2.0.2'),
+ Firmware('nanosp', '1.0.3'),
+]
+
+def pytest_addoption(parser):
+ parser.addoption("--backend", action="store", default="speculos")
+ parser.addoption("--display", action="store_true", default=False)
+ # Enable using --'device' in the pytest command line to restrict testing to specific devices
+ for fw in FIRMWARES:
+ parser.addoption("--"+fw.device, action="store_true", help="run on {} only".format(fw.device))
+
+
+@pytest.fixture(scope="session")
+def backend(pytestconfig):
+ return pytestconfig.getoption("backend")
+
+
+@pytest.fixture(scope="session")
+def display(pytestconfig):
+ return pytestconfig.getoption("display")
+
+
+# Glue to call every test that depends on the firmware once for each required firmware
+def pytest_generate_tests(metafunc):
+ if "firmware" in metafunc.fixturenames:
+ fw_list = []
+ ids = []
+ # First pass: enable only demanded firmwares
+ for fw in FIRMWARES:
+ if metafunc.config.getoption(fw.device):
+ fw_list.append(fw)
+ ids.append(fw.device + " " + fw.version)
+ # Second pass if no specific firmware demanded: add them all
+ if not fw_list:
+ for fw in FIRMWARES:
+ fw_list.append(fw)
+ ids.append(fw.device + " " + fw.version)
+ metafunc.parametrize("firmware", fw_list, ids=ids)
+
+
+def prepare_speculos_args(firmware, display: bool):
+ speculos_args = []
+
+ if display:
+ speculos_args += ["--display", "qt"]
+
+ app_path = app_path_from_app_name(APPS_DIRECTORY, APP_NAME, firmware.device)
+
+ return ([app_path], {"args": speculos_args})
+
+
+def create_backend(backend: str, firmware: Firmware, display: bool):
+ if backend.lower() == "ledgercomm":
+ return LedgerCommBackend(firmware, interface="hid")
+ elif backend.lower() == "ledgerwallet":
+ return LedgerWalletBackend(firmware)
+ elif backend.lower() == "speculos":
+ args, kwargs = prepare_speculos_args(firmware, display)
+ return SpeculosBackend(*args, firmware, **kwargs)
+ else:
+ raise ValueError(f"Backend '{backend}' is unknown. Valid backends are: {BACKENDS}")
+
+@pytest.fixture
+def client(backend, firmware, display: bool):
+ with create_backend(backend, firmware, display) as b:
+ yield b
+
+@pytest.fixture(autouse=True)
+def use_only_on_backend(request, backend):
+ if request.node.get_closest_marker('use_on_backend'):
+ current_backend = request.node.get_closest_marker('use_on_backend').args[0]
+ if current_backend != backend:
+ pytest.skip('skipped on this backend: {}'.format(current_backend))
+
+def pytest_configure(config):
+ config.addinivalue_line(
+ "markers", "use_only_on_backend(backend): skip test if not on the specified backend",
+ )
diff --git a/tests/elfs/hedera_nanos.elf b/tests/elfs/hedera_nanos.elf
new file mode 100755
index 00000000..6309ae92
Binary files /dev/null and b/tests/elfs/hedera_nanos.elf differ
diff --git a/tests/elfs/hedera_nanosplus.elf b/tests/elfs/hedera_nanosplus.elf
new file mode 100755
index 00000000..9ba1f2ca
Binary files /dev/null and b/tests/elfs/hedera_nanosplus.elf differ
diff --git a/tests/elfs/hedera_nanox.elf b/tests/elfs/hedera_nanox.elf
new file mode 100755
index 00000000..16bdff6f
Binary files /dev/null and b/tests/elfs/hedera_nanox.elf differ
diff --git a/tests/nanopb_pb2.py b/tests/nanopb_pb2.py
new file mode 120000
index 00000000..4c34e39b
--- /dev/null
+++ b/tests/nanopb_pb2.py
@@ -0,0 +1 @@
+../vendor/nanopb/generator/proto/nanopb_pb2.py
\ No newline at end of file
diff --git a/tests/pdm.lock b/tests/pdm.lock
new file mode 100644
index 00000000..51f7c840
--- /dev/null
+++ b/tests/pdm.lock
@@ -0,0 +1,1274 @@
+[[package]]
+name = "aniso8601"
+version = "9.0.1"
+summary = "A library for parsing ISO 8601 strings."
+
+[[package]]
+name = "asn1crypto"
+version = "1.5.1"
+summary = "Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP"
+
+[[package]]
+name = "attrs"
+version = "22.1.0"
+requires_python = ">=3.5"
+summary = "Classes Without Boilerplate"
+
+[[package]]
+name = "base58"
+version = "2.1.1"
+requires_python = ">=3.5"
+summary = "Base58 and Base58Check implementation."
+
+[[package]]
+name = "bip-utils"
+version = "2.7.0"
+requires_python = ">=3.7"
+summary = "Generation of mnemonics, seeds, private/public keys and addresses for different types of cryptocurrencies"
+dependencies = [
+ "cbor2~=5.1",
+ "coincurve<18.0.0,>=15.0.1",
+ "crcmod~=1.7",
+ "ecdsa~=0.15",
+ "ed25519-blake2b~=1.4",
+ "py-sr25519-bindings<2.0.0,>=0.1.3",
+ "pycryptodome~=3.6",
+ "pynacl~=1.4",
+]
+
+[[package]]
+name = "bip32"
+version = "3.3"
+summary = "Minimalistic implementation of the BIP32 key derivation scheme"
+dependencies = [
+ "base58~=2.0",
+ "coincurve<18,>=15.0",
+]
+
+[[package]]
+name = "black"
+version = "22.10.0"
+requires_python = ">=3.7"
+summary = "The uncompromising code formatter."
+dependencies = [
+ "click>=8.0.0",
+ "mypy-extensions>=0.4.3",
+ "pathspec>=0.9.0",
+ "platformdirs>=2",
+ "tomli>=1.1.0; python_full_version < \"3.11.0a7\"",
+]
+
+[[package]]
+name = "cbor2"
+version = "5.4.3"
+requires_python = ">=3.7"
+summary = "CBOR (de)serializer with extensive tag support"
+
+[[package]]
+name = "certifi"
+version = "2022.9.24"
+requires_python = ">=3.6"
+summary = "Python package for providing Mozilla's CA Bundle."
+
+[[package]]
+name = "cffi"
+version = "1.15.1"
+summary = "Foreign Function Interface for Python calling C code."
+dependencies = [
+ "pycparser",
+]
+
+[[package]]
+name = "charset-normalizer"
+version = "2.1.1"
+requires_python = ">=3.6.0"
+summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
+
+[[package]]
+name = "click"
+version = "8.1.3"
+requires_python = ">=3.7"
+summary = "Composable command line interface toolkit"
+dependencies = [
+ "colorama; platform_system == \"Windows\"",
+]
+
+[[package]]
+name = "coincurve"
+version = "17.0.0"
+requires_python = ">=3.7"
+summary = "Cross-platform Python CFFI bindings for libsecp256k1"
+dependencies = [
+ "asn1crypto",
+ "cffi>=1.3.0",
+]
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+summary = "Cross-platform colored terminal text."
+
+[[package]]
+name = "construct"
+version = "2.10.68"
+requires_python = ">=3.6"
+summary = "A powerful declarative symmetric parser/builder for binary data"
+
+[[package]]
+name = "coverage"
+version = "6.5.0"
+requires_python = ">=3.7"
+summary = "Code coverage measurement for Python"
+
+[[package]]
+name = "coverage"
+version = "6.5.0"
+extras = ["toml"]
+requires_python = ">=3.7"
+summary = "Code coverage measurement for Python"
+dependencies = [
+ "coverage==6.5.0",
+ "tomli; python_full_version <= \"3.11.0a6\"",
+]
+
+[[package]]
+name = "crcmod"
+version = "1.7"
+summary = "CRC Generator"
+
+[[package]]
+name = "ecdsa"
+version = "0.18.0"
+requires_python = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+summary = "ECDSA cryptographic signature library (pure python)"
+dependencies = [
+ "six>=1.9.0",
+]
+
+[[package]]
+name = "ed25519-blake2b"
+version = "1.4"
+summary = "Ed25519 public-key signatures (BLAKE2b fork)"
+
+[[package]]
+name = "exceptiongroup"
+version = "1.0.4"
+requires_python = ">=3.7"
+summary = "Backport of PEP 654 (exception groups)"
+
+[[package]]
+name = "flask"
+version = "2.2.2"
+requires_python = ">=3.7"
+summary = "A simple framework for building complex web applications."
+dependencies = [
+ "Jinja2>=3.0",
+ "Werkzeug>=2.2.2",
+ "click>=8.0",
+ "itsdangerous>=2.0",
+]
+
+[[package]]
+name = "flask-restful"
+version = "0.3.9"
+summary = "Simple framework for creating REST APIs"
+dependencies = [
+ "Flask>=0.8",
+ "aniso8601>=0.82",
+ "pytz",
+ "six>=1.3.0",
+]
+
+[[package]]
+name = "idna"
+version = "3.4"
+requires_python = ">=3.5"
+summary = "Internationalized Domain Names in Applications (IDNA)"
+
+[[package]]
+name = "iniconfig"
+version = "1.1.1"
+summary = "iniconfig: brain-dead simple config-ini parsing"
+
+[[package]]
+name = "itsdangerous"
+version = "2.1.2"
+requires_python = ">=3.7"
+summary = "Safely pass data to untrusted environments and back."
+
+[[package]]
+name = "jinja2"
+version = "3.1.2"
+requires_python = ">=3.7"
+summary = "A very fast and expressive template engine."
+dependencies = [
+ "MarkupSafe>=2.0",
+]
+
+[[package]]
+name = "jsonschema"
+version = "3.2.0"
+summary = "An implementation of JSON Schema validation for Python"
+dependencies = [
+ "attrs>=17.4.0",
+ "pyrsistent>=0.14.0",
+ "setuptools",
+ "six>=1.11.0",
+]
+
+[[package]]
+name = "markupsafe"
+version = "2.1.1"
+requires_python = ">=3.7"
+summary = "Safely add untrusted strings to HTML/XML markup."
+
+[[package]]
+name = "mnemonic"
+version = "0.20"
+requires_python = ">=3.5"
+summary = "Implementation of Bitcoin BIP-0039"
+
+[[package]]
+name = "mypy-extensions"
+version = "0.4.3"
+summary = "Experimental type system extensions for programs checked with the mypy typechecker."
+
+[[package]]
+name = "mypy-protobuf"
+version = "3.3.0"
+requires_python = ">=3.7"
+summary = "Generate mypy stub files from protobuf specs"
+dependencies = [
+ "protobuf>=3.19.4",
+ "types-protobuf>=3.19.12",
+]
+
+[[package]]
+name = "packaging"
+version = "21.3"
+requires_python = ">=3.6"
+summary = "Core utilities for Python packages"
+dependencies = [
+ "pyparsing!=3.0.5,>=2.0.2",
+]
+
+[[package]]
+name = "pathspec"
+version = "0.10.2"
+requires_python = ">=3.7"
+summary = "Utility library for gitignore style pattern matching of file paths."
+
+[[package]]
+name = "pillow"
+version = "9.3.0"
+requires_python = ">=3.7"
+summary = "Python Imaging Library (Fork)"
+
+[[package]]
+name = "platformdirs"
+version = "2.5.4"
+requires_python = ">=3.7"
+summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+
+[[package]]
+name = "pluggy"
+version = "1.0.0"
+requires_python = ">=3.6"
+summary = "plugin and hook calling mechanisms for python"
+
+[[package]]
+name = "protobuf"
+version = "3.20.3"
+requires_python = ">=3.7"
+summary = "Protocol Buffers"
+
+[[package]]
+name = "py-sr25519-bindings"
+version = "0.1.4"
+summary = "Python bindings for sr25519 library"
+
+[[package]]
+name = "pycparser"
+version = "2.21"
+requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+summary = "C parser in Python"
+
+[[package]]
+name = "pycryptodome"
+version = "3.15.0"
+requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+summary = "Cryptographic library for Python"
+
+[[package]]
+name = "pyelftools"
+version = "0.29"
+summary = "Library for analyzing ELF files and DWARF debugging information"
+
+[[package]]
+name = "pynacl"
+version = "1.5.0"
+requires_python = ">=3.6"
+summary = "Python binding to the Networking and Cryptography (NaCl) library"
+dependencies = [
+ "cffi>=1.4.1",
+]
+
+[[package]]
+name = "pyparsing"
+version = "3.0.9"
+requires_python = ">=3.6.8"
+summary = "pyparsing module - Classes and methods to define and execute parsing grammars"
+
+[[package]]
+name = "pyqt5"
+version = "5.15.7"
+requires_python = ">=3.7"
+summary = "Python bindings for the Qt cross platform application toolkit"
+dependencies = [
+ "PyQt5-Qt5>=5.15.0",
+ "PyQt5-sip<13,>=12.11",
+]
+
+[[package]]
+name = "pyqt5-qt5"
+version = "5.15.2"
+summary = "The subset of a Qt installation needed by PyQt5."
+
+[[package]]
+name = "pyqt5-sip"
+version = "12.11.0"
+requires_python = ">=3.7"
+summary = "The sip module support for PyQt5"
+
+[[package]]
+name = "pyrsistent"
+version = "0.19.2"
+requires_python = ">=3.7"
+summary = "Persistent/Functional/Immutable data structures"
+
+[[package]]
+name = "pytest"
+version = "7.2.0"
+requires_python = ">=3.7"
+summary = "pytest: simple powerful testing with Python"
+dependencies = [
+ "attrs>=19.2.0",
+ "colorama; sys_platform == \"win32\"",
+ "exceptiongroup>=1.0.0rc8; python_version < \"3.11\"",
+ "iniconfig",
+ "packaging",
+ "pluggy<2.0,>=0.12",
+ "tomli>=1.0.0; python_version < \"3.11\"",
+]
+
+[[package]]
+name = "pytest-cov"
+version = "4.0.0"
+requires_python = ">=3.6"
+summary = "Pytest plugin for measuring coverage."
+dependencies = [
+ "coverage[toml]>=5.2.1",
+ "pytest>=4.6",
+]
+
+[[package]]
+name = "pytz"
+version = "2022.6"
+summary = "World timezone definitions, modern and historical"
+
+[[package]]
+name = "ragger"
+version = "0.7.0"
+requires_python = ">=3.6"
+summary = "Testing framework using Speculos and LedgerComm as backends"
+dependencies = [
+ "bip-utils>=2.4.0",
+ "bip32>=3.3",
+ "py-sr25519-bindings==0.1.4",
+ "semver>=2.13.0",
+]
+
+[[package]]
+name = "ragger"
+version = "0.7.0"
+extras = ["speculos", "tests"]
+requires_python = ">=3.6"
+summary = "Testing framework using Speculos and LedgerComm as backends"
+dependencies = [
+ "pytest",
+ "pytest-cov",
+ "ragger==0.7.0",
+ "speculos>=0.1.128",
+]
+
+[[package]]
+name = "requests"
+version = "2.28.1"
+requires_python = ">=3.7, <4"
+summary = "Python HTTP for Humans."
+dependencies = [
+ "certifi>=2017.4.17",
+ "charset-normalizer<3,>=2",
+ "idna<4,>=2.5",
+ "urllib3<1.27,>=1.21.1",
+]
+
+[[package]]
+name = "semver"
+version = "2.13.0"
+requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+summary = "Python helper for Semantic Versioning (http://semver.org/)"
+
+[[package]]
+name = "setuptools"
+version = "65.5.1"
+requires_python = ">=3.7"
+summary = "Easily download, build, install, upgrade, and uninstall Python packages"
+
+[[package]]
+name = "six"
+version = "1.16.0"
+requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+summary = "Python 2 and 3 compatibility utilities"
+
+[[package]]
+name = "speculos"
+version = "0.1.157"
+requires_python = ">=3.6.0"
+summary = "Ledger Blue and Nano S/X application emulator"
+dependencies = [
+ "construct<3.0.0,>=2.10.56",
+ "flask-restful<1.0,>=0.3.9",
+ "flask<3.0.0,>=2.0.0",
+ "jsonschema<4.0.0,>=3.2.0",
+ "mnemonic<1.0,>=0.19",
+ "pillow<10.0.0,>=8.0.0",
+ "pyelftools<1.0,>=0.27",
+ "pyqt5<6.0.0,>=5.15.2",
+ "requests<3.0.0,>=2.25.1",
+]
+
+[[package]]
+name = "tomli"
+version = "2.0.1"
+requires_python = ">=3.7"
+summary = "A lil' TOML parser"
+
+[[package]]
+name = "types-protobuf"
+version = "3.20.4.5"
+summary = "Typing stubs for protobuf"
+
+[[package]]
+name = "urllib3"
+version = "1.26.12"
+requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4"
+summary = "HTTP library with thread-safe connection pooling, file post, and more."
+
+[[package]]
+name = "werkzeug"
+version = "2.2.2"
+requires_python = ">=3.7"
+summary = "The comprehensive WSGI web application library."
+dependencies = [
+ "MarkupSafe>=2.1.1",
+]
+
+[metadata]
+lock_version = "4.0"
+content_hash = "sha256:38717b41c12cd6cc82e65935b165b9fbaccb282850ed20fd7e7ab88120a0f1e0"
+
+[metadata.files]
+"aniso8601 9.0.1" = [
+ {url = "https://files.pythonhosted.org/packages/cb/72/be3db445b03944bfbb2b02b82d00cb2a2bcf96275c4543f14bf60fa79e12/aniso8601-9.0.1.tar.gz", hash = "sha256:72e3117667eedf66951bb2d93f4296a56b94b078a8a95905a052611fb3f1b973"},
+ {url = "https://files.pythonhosted.org/packages/e3/04/e97c12dc034791d7b504860acfcdd2963fa21ae61eaca1c9d31245f812c3/aniso8601-9.0.1-py2.py3-none-any.whl", hash = "sha256:1d2b7ef82963909e93c4f24ce48d4de9e66009a21bf1c1e1c85bdd0812fe412f"},
+]
+"asn1crypto 1.5.1" = [
+ {url = "https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67"},
+ {url = "https://files.pythonhosted.org/packages/de/cf/d547feed25b5244fcb9392e288ff9fdc3280b10260362fc45d37a798a6ee/asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c"},
+]
+"attrs 22.1.0" = [
+ {url = "https://files.pythonhosted.org/packages/1a/cb/c4ffeb41e7137b23755a45e1bfec9cbb76ecf51874c6f1d113984ecaa32c/attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"},
+ {url = "https://files.pythonhosted.org/packages/f2/bc/d817287d1aa01878af07c19505fafd1165cd6a119e9d0821ca1d1c20312d/attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"},
+]
+"base58 2.1.1" = [
+ {url = "https://files.pythonhosted.org/packages/4a/45/ec96b29162a402fc4c1c5512d114d7b3787b9d1c2ec241d9568b4816ee23/base58-2.1.1-py3-none-any.whl", hash = "sha256:11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2"},
+ {url = "https://files.pythonhosted.org/packages/7f/45/8ae61209bb9015f516102fa559a2914178da1d5868428bd86a1b4421141d/base58-2.1.1.tar.gz", hash = "sha256:c5d0cb3f5b6e81e8e35da5754388ddcc6d0d14b6c6a132cb93d69ed580a7278c"},
+]
+"bip-utils 2.7.0" = [
+ {url = "https://files.pythonhosted.org/packages/e7/2f/baba669c680fef1e841d05840f4089d24841cd485fa4b0c7ce4bedd24d25/bip_utils-2.7.0.tar.gz", hash = "sha256:bc6302840a95695609e215ad362ddb42d70d472b3cb1494d1fb2112d08c1c707"},
+ {url = "https://files.pythonhosted.org/packages/f7/5f/0327b3d8938453ebfdbf1e9b95f69f5c0efe533536950371517c17cee38a/bip_utils-2.7.0-py3-none-any.whl", hash = "sha256:b04c0f60628f1ab2792e8946f8547515b72269d8f431f722592f6a71fa7ff9fc"},
+]
+"bip32 3.3" = [
+ {url = "https://files.pythonhosted.org/packages/61/b1/37685f64de6d6f5ede0394fe70a9ab45f4f0691a2fe99a231f1b569e1c6d/bip32-3.3-py3-none-any.whl", hash = "sha256:c1ed0b2896b090e090cbd3c7de94ed10a9e1f9157e264f09f1a7177cb0b31b6d"},
+ {url = "https://files.pythonhosted.org/packages/7e/ec/bc151473168920d24ca6af7288dd5cdacb71c69cf6d357b146945777c4b2/bip32-3.3.tar.gz", hash = "sha256:664e0700ee53c79bf2573b277f278b7f4a6ca14ee4ce5e8499721c4c752e32c1"},
+]
+"black 22.10.0" = [
+ {url = "https://files.pythonhosted.org/packages/2c/11/f2737cd3b458d91401801e83a014e87c63e8904dc063200f77826c352f54/black-22.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb"},
+ {url = "https://files.pythonhosted.org/packages/3d/c5/b3ab9b563f35fb284d37ab2b14acaed9a27d8cdea9c31364766eb54946a7/black-22.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d"},
+ {url = "https://files.pythonhosted.org/packages/56/df/913d71817c7034edba25d596c54f782c2f809b6af30367d2f00309e8890a/black-22.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d"},
+ {url = "https://files.pythonhosted.org/packages/69/21/846c95710cc6561ba980bd6c72479dbcdde742e927ff5ef7340916d003ac/black-22.10.0-1fixedarch-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d"},
+ {url = "https://files.pythonhosted.org/packages/69/84/903cdf41514088d5a716538cb189c471ab34e56ae9a1c2da6b8bfe8e4dbf/black-22.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0"},
+ {url = "https://files.pythonhosted.org/packages/71/f8/57e47ea67f59613c4368a952062bc3429131249920cffbb8362fd404b733/black-22.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff"},
+ {url = "https://files.pythonhosted.org/packages/86/da/edebcc6c13441d91eff6761e50512bc6d6886a556dc5357b399694122b4f/black-22.10.0-1fixedarch-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6"},
+ {url = "https://files.pythonhosted.org/packages/91/e6/d9b78987d7d903369ba1a0b795bce4de06f0155be6609f15e8950aef8f7e/black-22.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395"},
+ {url = "https://files.pythonhosted.org/packages/a3/89/629fca2eea0899c06befaa58dc0f49d56807d454202bb2e54bd0d98c77f3/black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"},
+ {url = "https://files.pythonhosted.org/packages/a5/5f/9cfc6dd95965f8df30194472543e6f0515a10d78ea5378426ef1546735c7/black-22.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7"},
+ {url = "https://files.pythonhosted.org/packages/a6/84/5c3f3ffc4143fa7e208d745d2239d915e74d3709fdbc64c3e98d3fd27e56/black-22.10.0-1fixedarch-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef"},
+ {url = "https://files.pythonhosted.org/packages/ab/15/61119d166a44699827c112d7c4726421f14323c2cb7aa9f4c26628f237f9/black-22.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de"},
+ {url = "https://files.pythonhosted.org/packages/ae/49/ea03c318a25be359b8e5178a359d47e2da8f7524e1522c74b8f74c66b6f8/black-22.10.0-1fixedarch-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa"},
+ {url = "https://files.pythonhosted.org/packages/b0/9e/fa912c5ae4b8eb6d36982fc8ac2d779cf944dbd7c3c1fe7a28acf462c1ed/black-22.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b"},
+ {url = "https://files.pythonhosted.org/packages/b9/51/403b0b0eb9fb412ca02b79dc38472469f2f88c9aacc6bb5262143e4ff0bc/black-22.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383"},
+ {url = "https://files.pythonhosted.org/packages/ce/6f/74492b8852ee4f2ad2178178f6b65bc8fc80ad539abe56c1c23eab6732e2/black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"},
+ {url = "https://files.pythonhosted.org/packages/d0/5a/5f31494e3acbb6319ee60c3a3a09d3e536a3fd2353f76af9cbff799c4999/black-22.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87"},
+ {url = "https://files.pythonhosted.org/packages/e2/2f/a8406a9e337a213802aa90a3e9fbf90c86f3edce92f527255fd381309b77/black-22.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae"},
+ {url = "https://files.pythonhosted.org/packages/e3/b4/9203f1a0c99aa30389b61fa8cb54bc9f4bf16ac3aa74630c6b974ed3f3b0/black-22.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650"},
+ {url = "https://files.pythonhosted.org/packages/f2/23/f4278377cabf882298b4766e977fd04377f288d1ccef706953076a1e0598/black-22.10.0-1fixedarch-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4"},
+ {url = "https://files.pythonhosted.org/packages/ff/ce/22281871536b3d79474fd44d48dad48f7cbc5c3982bddf6a7495e7079d00/black-22.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66"},
+]
+"cbor2 5.4.3" = [
+ {url = "https://files.pythonhosted.org/packages/06/bc/a06502ec2156236bf3de33d88a8afcad31834fb2a315f4762710af5e551e/cbor2-5.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e10f2f4fcf5ab6a8b24d22f7109f48cad8143f669795899370170d7b36ed309f"},
+ {url = "https://files.pythonhosted.org/packages/0b/79/30c4336c03e2f22cf4780d680af89b3d1a7b20dcf935a745ec1f6920c992/cbor2-5.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37ae0ce5afe864d1a1c5b05becaf8aaca7b7131cb7b0b935d7e79b29fb1cea28"},
+ {url = "https://files.pythonhosted.org/packages/0e/74/0c5b7d875031cd39734a9eb369d32560712512e68fd520408a02e59e366a/cbor2-5.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:4e8590193fcbbb9477010ca0f094f6540a5e723965c90eea7a37edbe75f0ec4d"},
+ {url = "https://files.pythonhosted.org/packages/0f/58/d8fa51d2fb85fdcc539a7e434dc91f5a53a40676ab04ccfb3c2105694d8b/cbor2-5.4.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:981b9ffc4f2947a0f030e71ce5eac31334bc81369dd57c6c1273c94c6cdb0b5a"},
+ {url = "https://files.pythonhosted.org/packages/17/9f/649aa0e5927ffb0f5920d5b4ab2c9236d3c7243f6aae6a79dc8239dbbce5/cbor2-5.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:0a3a1b2f6b83ab4ce806df48360cc16d34cd315f17549dbda9fdd371bea04497"},
+ {url = "https://files.pythonhosted.org/packages/19/58/0caea0a5bc265b8eb09cb633873be06ea8bf4779a5338e42d389c9113c37/cbor2-5.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:5c50da4702ac5ca3a8e7cb9f34f62b4ea91bc81b76c2fba03888b366da299cd8"},
+ {url = "https://files.pythonhosted.org/packages/3b/e6/6d070295033b286b79a066577c4e2f32a30da87b9080cdcbb4bff0bb02c5/cbor2-5.4.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c07975f956baddb8dfeca4966f1871fd2482cb36af24c461f763732a44675225"},
+ {url = "https://files.pythonhosted.org/packages/45/aa/078ff9bb5f6075289434db76f8f0ab3f0e0815a7c8d1b42250983df552a7/cbor2-5.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:c617c7f94936d65ed9c8e99c6c03e3dc83313d69c6bfea810014ec658e9b1a9d"},
+ {url = "https://files.pythonhosted.org/packages/69/3e/23c195efcc1a09c0cb3ad3cf255ac628290e69c325a6094427552faf1bc2/cbor2-5.4.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9538ab1b4207e76ee02a52362d77e312921ec1dc75b6fb42182887d87d0ca53e"},
+ {url = "https://files.pythonhosted.org/packages/6b/b3/ba530faf197f4fa76715f65f375af50e36848468e9154ae4847a7a9da0ef/cbor2-5.4.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0e4ae67a697c664b579b87c4ef9d60e26c146b95bff443a9a38abb16f6981ff0"},
+ {url = "https://files.pythonhosted.org/packages/7a/7a/95def2895c54362eeec30b336b6b00b9a7e5d77ddce5f15902f8f15f75c7/cbor2-5.4.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab6c934806759d453a9bb5318f2703c831e736be005ac35d5bd5cf2093ba57b1"},
+ {url = "https://files.pythonhosted.org/packages/7d/80/cd612cc982cb1e3b4e801d241dec977a7e60acdef204862e19ab5607e2f2/cbor2-5.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:70789805b9aebd215626188aa05bb09908ed51e3268d4db5ae6a08276efdbcb1"},
+ {url = "https://files.pythonhosted.org/packages/81/2b/c9f71180c14cc8009b8ea3a4a33e5a5e82cd14f78d5bd3e53e465ae54da0/cbor2-5.4.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b35c5d4d14fe804f718d5a5968a528970d2a7046aa87045538f189a98e5c7055"},
+ {url = "https://files.pythonhosted.org/packages/92/15/c8df882191534848c094cdba1ee43c24a1f203158317055b88b5c1ef8b9c/cbor2-5.4.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:20291dad09cf9c4e5f434d376dd9d60f5ab5e066b308005f50e7c5e22e504214"},
+ {url = "https://files.pythonhosted.org/packages/92/b7/d68849bb65dfd6fa359be214451e714f433973f16123b1454bdd24c9dc1c/cbor2-5.4.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cbe7cdeed26cd8ec2dcfed2b8876bc137ad8b9e0abb07aa5fb05770148a4b5c7"},
+ {url = "https://files.pythonhosted.org/packages/96/39/e4c02d596fbfe74cb87435648b278f08298ad12547501c8f4ffddd2d7839/cbor2-5.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8a643b19ace1584043bbf4e2d0b4fae8bebd6b6ffab14ea6478d3ff07f58e854"},
+ {url = "https://files.pythonhosted.org/packages/96/d5/1636607ae4080bec89bded6a7191245449555d1f64c053c90578634b3362/cbor2-5.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4b09ff6148a8cd529512479a1d6521fb7687fb03b448973933c3b03711d00bfc"},
+ {url = "https://files.pythonhosted.org/packages/9c/08/bd9e416077e1f35de5ab9d5616eba237922f679287a4dcc60f3f78b69c9c/cbor2-5.4.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5aaf3406c9d661d11f87e792edb9a38561dba1441afba7fb883d6d963e67f32c"},
+ {url = "https://files.pythonhosted.org/packages/9d/c9/cfa5c35a62642a19c14bf9a12dfbf0ee134466be1f062df2258a2ec2f2f7/cbor2-5.4.3.tar.gz", hash = "sha256:62b863c5ee6ced4032afe948f3c1484f375550995d3b8498145237fe28e546c2"},
+ {url = "https://files.pythonhosted.org/packages/ac/11/b163ea396a02a2e27bdbd63a4e44d317feecd2cb72094a61977dbdf6bb82/cbor2-5.4.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62fc15bfe187e4994c457e6055687514c417d6099de62dd33ae766561f05847e"},
+ {url = "https://files.pythonhosted.org/packages/ae/52/2e690a63afb3822da5825048a47accaccddf713d7148e2696fe0445c1215/cbor2-5.4.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:cbca58220f52fd50d8985e4079e10c71196d538fb6685f157f608a29253409a4"},
+ {url = "https://files.pythonhosted.org/packages/af/ec/2f48f091d7be9ea98aed93ee3b74bc544295efef65f0b38fa68fe4a03e72/cbor2-5.4.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d21ccd1ec802e88dba1c373724a09538a0237116ab589c5301ca4c59478f7c10"},
+ {url = "https://files.pythonhosted.org/packages/b4/6c/02b19eeafb7161f70516d5caf385f9d27eab2d20abd4d462ccd38fc2eecf/cbor2-5.4.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fab0e00c28305db59f7005150447d08dd13da6a82695a2132c28beba590fd2c"},
+ {url = "https://files.pythonhosted.org/packages/b5/65/5a5e224bc3f0fa78c6a723f64a30c129e7a0c770584a8cd31e7b3dcbe4fa/cbor2-5.4.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3843a9bb970343e9c896aa71a34fa80983cd0ddec6eacdb2284b5e83f4ee7511"},
+ {url = "https://files.pythonhosted.org/packages/c4/1a/5d8f054f79e40bd864cb05a4e0bddd0b99d87400a14e7ba68de27d87206d/cbor2-5.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de925608dc6d73cd1aab08800bff38f71f90459c15db3a71a67023b0fc697da"},
+ {url = "https://files.pythonhosted.org/packages/cd/7a/258d271ffbac9a6222f79c91f84c5a0ff2fdc71a158286e22e3477b292cb/cbor2-5.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2f30f7ef329ea6ec630ceabe5a539fed407b9c81e27e2322644e3efbbd1b2a76"},
+ {url = "https://files.pythonhosted.org/packages/ee/e5/6d3a4a6ebc47554b9042c4b934f2d3d0111abbf331c545b016df293036c8/cbor2-5.4.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6bc8c5606aa0ae510bdb3c7d987f92df39ef87d09e0f0588a4d1daffd3cb0453"},
+ {url = "https://files.pythonhosted.org/packages/fc/90/47c8068827cca8df95b5ce603f44cff8be525bcf7792b465f5a822892c4a/cbor2-5.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d549abea7115c8a0d7c61a31a895c031f902a7b4c875f9efd8ce41e466baf83a"},
+]
+"certifi 2022.9.24" = [
+ {url = "https://files.pythonhosted.org/packages/1d/38/fa96a426e0c0e68aabc68e896584b83ad1eec779265a028e156ce509630e/certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"},
+ {url = "https://files.pythonhosted.org/packages/cb/a4/7de7cd59e429bd0ee6521ba58a75adaec136d32f91a761b28a11d8088d44/certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"},
+]
+"cffi 1.15.1" = [
+ {url = "https://files.pythonhosted.org/packages/00/05/23a265a3db411b0bfb721bf7a116c7cecaf3eb37ebd48a6ea4dfb0a3244d/cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"},
+ {url = "https://files.pythonhosted.org/packages/03/7b/259d6e01a6083acef9d3c8c88990c97d313632bb28fa84d6ab2bb201140a/cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"},
+ {url = "https://files.pythonhosted.org/packages/0e/65/0d7b5dad821ced4dcd43f96a362905a68ce71e6b5f5cfd2fada867840582/cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"},
+ {url = "https://files.pythonhosted.org/packages/0e/e2/a23af3d81838c577571da4ff01b799b0c2bbde24bd924d97e228febae810/cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"},
+ {url = "https://files.pythonhosted.org/packages/10/72/617ee266192223a38b67149c830bd9376b69cf3551e1477abc72ff23ef8e/cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"},
+ {url = "https://files.pythonhosted.org/packages/18/8f/5ff70c7458d61fa8a9752e5ee9c9984c601b0060aae0c619316a1e1f1ee5/cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"},
+ {url = "https://files.pythonhosted.org/packages/1d/76/bcebbbab689f5f6fc8a91e361038a3001ee2e48c5f9dbad0a3b64a64cc9e/cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"},
+ {url = "https://files.pythonhosted.org/packages/22/c6/df826563f55f7e9dd9a1d3617866282afa969fe0d57decffa1911f416ed8/cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"},
+ {url = "https://files.pythonhosted.org/packages/23/8b/2e8c2469eaf89f7273ac685164949a7e644cdfe5daf1c036564208c3d26b/cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"},
+ {url = "https://files.pythonhosted.org/packages/2b/a8/050ab4f0c3d4c1b8aaa805f70e26e84d0e27004907c5b8ecc1d31815f92a/cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"},
+ {url = "https://files.pythonhosted.org/packages/2d/86/3ca57cddfa0419f6a95d1c8478f8f622ba597e3581fd501bbb915b20eb75/cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"},
+ {url = "https://files.pythonhosted.org/packages/2e/7a/68c35c151e5b7a12650ecc12fdfb85211aa1da43e9924598451c4a0a3839/cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"},
+ {url = "https://files.pythonhosted.org/packages/32/2a/63cb8c07d151de92ff9d897b2eb27ba6a0e78dda8e4c5f70d7b8c16cd6a2/cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"},
+ {url = "https://files.pythonhosted.org/packages/32/bd/d0809593f7976828f06a492716fbcbbfb62798bbf60ea1f65200b8d49901/cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"},
+ {url = "https://files.pythonhosted.org/packages/37/5a/c37631a86be838bdd84cc0259130942bf7e6e32f70f4cab95f479847fb91/cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"},
+ {url = "https://files.pythonhosted.org/packages/3a/12/d6066828014b9ccb2bbb8e1d9dc28872d20669b65aeb4a86806a0757813f/cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"},
+ {url = "https://files.pythonhosted.org/packages/3a/75/a162315adeaf47e94a3b7f886a8e31d77b9e525a387eef2d6f0efc96a7c8/cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"},
+ {url = "https://files.pythonhosted.org/packages/3f/fa/dfc242febbff049509e5a35a065bdc10f90d8c8585361c2c66b9c2f97a01/cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"},
+ {url = "https://files.pythonhosted.org/packages/43/a0/cc7370ef72b6ee586369bacd3961089ab3d94ae712febf07a244f1448ffd/cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"},
+ {url = "https://files.pythonhosted.org/packages/47/51/3049834f07cd89aceef27f9c56f5394ca6725ae6a15cff5fbdb2f06a24ad/cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"},
+ {url = "https://files.pythonhosted.org/packages/47/97/137f0e3d2304df2060abb872a5830af809d7559a5a4b6a295afb02728e65/cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"},
+ {url = "https://files.pythonhosted.org/packages/50/34/4cc590ad600869502c9838b4824982c122179089ed6791a8b1c95f0ff55e/cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"},
+ {url = "https://files.pythonhosted.org/packages/5b/1a/e1ee5bed11d8b6540c05a8e3c32448832d775364d4461dd6497374533401/cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"},
+ {url = "https://files.pythonhosted.org/packages/5d/4e/4e0bb5579b01fdbfd4388bd1eb9394a989e1336203a4b7f700d887b233c1/cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"},
+ {url = "https://files.pythonhosted.org/packages/5d/6f/3a2e167113eabd46ed300ff3a6a1e9277a3ad8b020c4c682f83e9326fcf7/cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"},
+ {url = "https://files.pythonhosted.org/packages/69/bf/335f8d95510b1a26d7c5220164dc739293a71d5540ecd54a2f66bac3ecb8/cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"},
+ {url = "https://files.pythonhosted.org/packages/71/d7/0fe0d91b0bbf610fb7254bb164fa8931596e660d62e90fb6289b7ee27b09/cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"},
+ {url = "https://files.pythonhosted.org/packages/77/b7/d3618d612be01e184033eab90006f8ca5b5edafd17bf247439ea4e167d8a/cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"},
+ {url = "https://files.pythonhosted.org/packages/79/4b/33494eb0adbcd884656c48f6db0c98ad8a5c678fb8fb5ed41ab546b04d8c/cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"},
+ {url = "https://files.pythonhosted.org/packages/7c/3e/5d823e5bbe00285e479034bcad44177b7353ec9fdcd7795baac5ccf82950/cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"},
+ {url = "https://files.pythonhosted.org/packages/85/1f/a3c533f8d377da5ca7edb4f580cc3edc1edbebc45fac8bb3ae60f1176629/cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"},
+ {url = "https://files.pythonhosted.org/packages/87/4b/64e8bd9d15d6b22b6cb11997094fbe61edf453ea0a97c8675cb7d1c3f06f/cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"},
+ {url = "https://files.pythonhosted.org/packages/87/ee/ddc23981fc0f5e7b5356e98884226bcb899f95ebaefc3e8e8b8742dd7e22/cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"},
+ {url = "https://files.pythonhosted.org/packages/88/89/c34caf63029fb7628ec2ebd5c88ae0c9bd17db98c812e4065a4d020ca41f/cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"},
+ {url = "https://files.pythonhosted.org/packages/91/bc/b7723c2fe7a22eee71d7edf2102cd43423d5f95ff3932ebaa2f82c7ec8d0/cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"},
+ {url = "https://files.pythonhosted.org/packages/93/d0/2e2b27ea2f69b0ec9e481647822f8f77f5fc23faca2dd00d1ff009940eb7/cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"},
+ {url = "https://files.pythonhosted.org/packages/9f/52/1e2b43cfdd7d9a39f48bc89fcaee8d8685b1295e205a4f1044909ac14d89/cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"},
+ {url = "https://files.pythonhosted.org/packages/a4/42/54bdf22cf6c8f95113af645d0bd7be7f9358ea5c2d57d634bb11c6b4d0b2/cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"},
+ {url = "https://files.pythonhosted.org/packages/a8/16/06b84a7063a4c0a2b081030fdd976022086da9c14e80a9ed4ba0183a98a9/cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"},
+ {url = "https://files.pythonhosted.org/packages/a9/ba/e082df21ebaa9cb29f2c4e1d7e49a29b90fcd667d43632c6674a16d65382/cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"},
+ {url = "https://files.pythonhosted.org/packages/aa/02/ab15b3aa572759df752491d5fa0f74128cd14e002e8e3257c1ab1587810b/cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"},
+ {url = "https://files.pythonhosted.org/packages/ad/26/7b3a73ab7d82a64664c7c4ea470e4ec4a3c73bb4f02575c543a41e272de5/cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"},
+ {url = "https://files.pythonhosted.org/packages/af/cb/53b7bba75a18372d57113ba934b27d0734206c283c1dfcc172347fbd9f76/cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"},
+ {url = "https://files.pythonhosted.org/packages/af/da/9441d56d7dd19d07dcc40a2a5031a1f51c82a27cee3705edf53dadcac398/cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"},
+ {url = "https://files.pythonhosted.org/packages/b3/b8/89509b6357ded0cbacc4e430b21a4ea2c82c2cdeb4391c148b7c7b213bed/cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"},
+ {url = "https://files.pythonhosted.org/packages/b5/7d/df6c088ef30e78a78b0c9cca6b904d5abb698afb5bc8f5191d529d83d667/cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"},
+ {url = "https://files.pythonhosted.org/packages/b5/80/ce5ba093c2475a73df530f643a61e2969a53366e372b24a32f08cd10172b/cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"},
+ {url = "https://files.pythonhosted.org/packages/b7/8b/06f30caa03b5b3ac006de4f93478dbd0239e2a16566d81a106c322dc4f79/cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"},
+ {url = "https://files.pythonhosted.org/packages/b9/4a/dde4d093a3084d0b0eadfb2703f71e31a5ced101a42c839ac5bbbd1710f2/cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"},
+ {url = "https://files.pythonhosted.org/packages/c1/25/16a082701378170559bb1d0e9ef2d293cece8dc62913d79351beb34c5ddf/cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"},
+ {url = "https://files.pythonhosted.org/packages/c2/0b/3b09a755ddb977c167e6d209a7536f6ade43bb0654bad42e08df1406b8e4/cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"},
+ {url = "https://files.pythonhosted.org/packages/c5/ff/3f9d73d480567a609e98beb0c64359f8e4f31cb6a407685da73e5347b067/cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"},
+ {url = "https://files.pythonhosted.org/packages/c6/3d/dd085bb831b22ce4d0b7ba8550e6d78960f02f770bbd1314fea3580727f8/cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"},
+ {url = "https://files.pythonhosted.org/packages/c9/e3/0a52838832408cfbbf3a59cb19bcd17e64eb33795c9710ca7d29ae10b5b7/cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"},
+ {url = "https://files.pythonhosted.org/packages/d3/56/3e94aa719ae96eeda8b68b3ec6e347e0a23168c6841dc276ccdcdadc9f32/cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"},
+ {url = "https://files.pythonhosted.org/packages/d3/e1/e55ca2e0dd446caa2cc8f73c2b98879c04a1f4064ac529e1836683ca58b8/cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"},
+ {url = "https://files.pythonhosted.org/packages/da/ff/ab939e2c7b3f40d851c0f7192c876f1910f3442080c9c846532993ec3cef/cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"},
+ {url = "https://files.pythonhosted.org/packages/df/02/aef53d4aa43154b829e9707c8c60bab413cd21819c4a36b0d7aaa83e2a61/cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"},
+ {url = "https://files.pythonhosted.org/packages/e8/ff/c4b7a358526f231efa46a375c959506c87622fb4a2c5726e827c55e6adf2/cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"},
+ {url = "https://files.pythonhosted.org/packages/ea/be/c4ad40ad441ac847b67c7a37284ae3c58f39f3e638c6b0f85fb662233825/cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"},
+ {url = "https://files.pythonhosted.org/packages/ed/a3/c5f01988ddb70a187c3e6112152e01696188c9f8a4fa4c68aa330adbb179/cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"},
+ {url = "https://files.pythonhosted.org/packages/ef/41/19da352d341963d29a33bdb28433ba94c05672fb16155f794fad3fd907b0/cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"},
+ {url = "https://files.pythonhosted.org/packages/f9/96/fc9e118c47b7adc45a0676f413b4a47554e5f3b6c99b8607ec9726466ef1/cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"},
+ {url = "https://files.pythonhosted.org/packages/ff/fe/ac46ca7b00e9e4f9c62e7928a11bc9227c86e2ff43526beee00cdfb4f0e8/cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"},
+ {url = "https://test-files.pythonhosted.org/packages/de/b0/ee80a110b7efabc2e7a952973c74c1bbffd1e4eda707f9e932fcd53fdca4/cffi-1.15.1-cp311-cp311-win_arm64.whl", hash = "sha256:e2fb0d88adc12260498e8c291b20d4ecf8f74dea78c5cd3d5880c1e68158345f"},
+]
+"charset-normalizer 2.1.1" = [
+ {url = "https://files.pythonhosted.org/packages/a1/34/44964211e5410b051e4b8d2869c470ae8a68ae274953b1c7de6d98bbcf94/charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"},
+ {url = "https://files.pythonhosted.org/packages/db/51/a507c856293ab05cdc1db77ff4bc1268ddd39f29e7dc4919aa497f0adbec/charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"},
+]
+"click 8.1.3" = [
+ {url = "https://files.pythonhosted.org/packages/59/87/84326af34517fca8c58418d148f2403df25303e02736832403587318e9e8/click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
+ {url = "https://files.pythonhosted.org/packages/c2/f1/df59e28c642d583f7dacffb1e0965d0e00b218e0186d7858ac5233dce840/click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
+ {url = "https://test-files.pythonhosted.org/packages/59/87/84326af34517fca8c58418d148f2403df25303e02736832403587318e9e8/click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
+ {url = "https://test-files.pythonhosted.org/packages/c2/f1/df59e28c642d583f7dacffb1e0965d0e00b218e0186d7858ac5233dce840/click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
+]
+"coincurve 17.0.0" = [
+ {url = "https://files.pythonhosted.org/packages/02/73/3e04569c0cb7478383710d1f7612e8da4d79eac1aa18522ac44f7dae5ec6/coincurve-17.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6aec70238dbe7a5d66b5f9438ff45b08eb5e0990d49c32ebb65247c5d5b89d7a"},
+ {url = "https://files.pythonhosted.org/packages/08/63/f2510bae1af0ce4bf6f6272bbe27fd20876695362b20d5df114d9295f7c5/coincurve-17.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1bef812da1da202cdd601a256825abcf26d86e8634fac3ec3e615e3bb3ff08c"},
+ {url = "https://files.pythonhosted.org/packages/28/cf/eccd05c927792538bfe3e3357c9ad07584d288a3984ff55527e268b50a64/coincurve-17.0.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f47806527d3184da3e8b146fac92a8ed567bbd225194f4517943d8cdc85f9542"},
+ {url = "https://files.pythonhosted.org/packages/29/06/b84a8ea38c24f168b8674fda3f87e99478692df52856dd43b692a57ad740/coincurve-17.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0503326963916c85b61d16f611ea0545f03c9e418fa8007c233c815429e381e8"},
+ {url = "https://files.pythonhosted.org/packages/29/94/293294bd71a0370a6dae079773db9cd2aed825ff1df975cb865158a1f0ec/coincurve-17.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1013c1597b65684ae1c3e42497f9ef5a04527fa6136a84a16b34602606428c74"},
+ {url = "https://files.pythonhosted.org/packages/34/8e/5b705213841580f5f1378f7e9a8766b2d698037bc84892db8d1a41c87deb/coincurve-17.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:74cedb3d3a1dc5abe0c9c2396e1b82cc64496babc5b42e007e72e185cb1edad8"},
+ {url = "https://files.pythonhosted.org/packages/3f/01/970679d7c3c152a6710b550298d4d09e415c9f5cfdbcc7fb95b282bed5fa/coincurve-17.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c71caffb97dd3d0c243beb62352669b1e5dafa3a4bccdbb27d36bd82f5e65d20"},
+ {url = "https://files.pythonhosted.org/packages/63/e9/31d148c8333d43cd5f0d406ddd5baf43be4dac69fcf96590150e80f649da/coincurve-17.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b88642edf7f281649b0c0b6ffade051945ccceae4b885e40445634877d0b3049"},
+ {url = "https://files.pythonhosted.org/packages/65/5f/2c2b9d86f62b3453104e63aa2aaed987a58762b8101e0d878f6bd7880995/coincurve-17.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac8c87d6fd080faa74e7ecf64a6ed20c11a254863238759eb02c3f13ad12b0c4"},
+ {url = "https://files.pythonhosted.org/packages/65/e9/e64d29c3f1ec9fe0fe4a114b95556e23d63bde0d48ec80e5ba22a3531a1f/coincurve-17.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:db874c5c1dcb1f3a19379773b5e8cffc777625a7a7a60dd9a67206e31e62e2e9"},
+ {url = "https://files.pythonhosted.org/packages/66/32/8c15a00d4452f65e01b18cf543d49897882b4a8a48e7f65470ce152a2696/coincurve-17.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:896b01941254f0a218cf331a9bddfe2d43892f7f1ba10d6e372e2eb744a744c2"},
+ {url = "https://files.pythonhosted.org/packages/66/e3/43c2766e850ad6dad13bf4b2005f22007f33f300064fe444f0f832381d7a/coincurve-17.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8852dc01af4f0fe941ffd04069f7e4fecdce9b867a016f823a02286a1a1f07b5"},
+ {url = "https://files.pythonhosted.org/packages/69/1c/d6f976de439a1e98faea23a6dee96cfaf47ea01c94b87335efbaa2260fb8/coincurve-17.0.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e2c2e8a1f0b1f8e48049c891af4ae3cad65d115d358bde72f6b8abdbb8a23170"},
+ {url = "https://files.pythonhosted.org/packages/83/a3/98a8e1cbad2b12f869966ce14f31c722dbf116b20dea6f3f8139b1b655d1/coincurve-17.0.0-py3-none-win_amd64.whl", hash = "sha256:630388080da3026e0b0176cc6762eaabecba857ee3fc85767577dea063ea7c6e"},
+ {url = "https://files.pythonhosted.org/packages/88/4d/16a487d4fc1d854b7266d208af2507e4babd07f52d5ede1a7b4c07a233ae/coincurve-17.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:25dfa105beba24c8de886f8ed654bb1133866e4e22cfd7ea5ad8438cae6ed924"},
+ {url = "https://files.pythonhosted.org/packages/89/16/6bf9aa224615cafedc319cf5ed355d3a984144bbc82a9e7d1d19da97afd8/coincurve-17.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4beef321fd6434448aab03a0c245f31c4e77f43b54b82108c0948d29852ac7e"},
+ {url = "https://files.pythonhosted.org/packages/8a/24/4b218c110341cadb728efed4e0ea40450dd7e1cc080b574673ac96f5095e/coincurve-17.0.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ea057f777842396d387103c606babeb3a1b4c6126769cc0a12044312fc6c465"},
+ {url = "https://files.pythonhosted.org/packages/8d/0b/317a2d20a29cd296a5d19abb6f642d067b2a596695781a8051a01c8ed070/coincurve-17.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:73f39579dd651a9fc29da5a8fc0d8153d872bcbc166f876457baced1a1c01501"},
+ {url = "https://files.pythonhosted.org/packages/94/6a/60401d0723aff65138e6eba7be09f63998ac96bfb64509c4aa020541abd6/coincurve-17.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f1ef72574aa423bc33665ef4be859164a478bad24d48442da874ef3dc39a474d"},
+ {url = "https://files.pythonhosted.org/packages/95/79/646c5fc4fc204c11a2129ab8ef2fdda9e64ce2f4ce04b79a274899ae5d84/coincurve-17.0.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d154e2eb5711db8c5ef52fcd80935b5a0e751c057bc6ffb215a7bb409aedef03"},
+ {url = "https://files.pythonhosted.org/packages/98/9f/da66d4a851925110dc03371db036551074a039700c089ce5f94af3ac7842/coincurve-17.0.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:747215254e51dd4dfbe6dded9235491263da5d88fe372d66541ca16b51ea078f"},
+ {url = "https://files.pythonhosted.org/packages/9e/f2/482210b2330fb453fd84b7309b9b79e49e6f17dd0efa70ff4d427156f144/coincurve-17.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad2f6df39ba1e2b7b14bb984505ffa7d0a0ecdd697e8d7dbd19e04bc245c87ed"},
+ {url = "https://files.pythonhosted.org/packages/a8/8d/74eb01cbf256ecb10140bf67e1a2e0f5ac1751fbc1347bc7e52f399f2b89/coincurve-17.0.0.tar.gz", hash = "sha256:68da55aff898702952fda3ee04fd6ed60bb6b91f919c69270786ed766b548b93"},
+ {url = "https://files.pythonhosted.org/packages/b8/1c/558879c99e3abdea2a870cac35601fe8957b6006c8ff76851968326c5f61/coincurve-17.0.0-py3-none-win32.whl", hash = "sha256:b956b0b2c85e25a7d00099970ff5d8338254b45e46f0a940f4a2379438ce0dde"},
+ {url = "https://files.pythonhosted.org/packages/b9/e5/8f7b2d8e9d777b42e16eca3e8ab308953cf4539eb5e04e7bb1200fe752e4/coincurve-17.0.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3d694ad194bee9e8792e2e75879dc5238d8a184010cde36c5ad518fcfe2cd8f2"},
+ {url = "https://files.pythonhosted.org/packages/c9/10/f0d5eaa344fd147bda5e80d254807a5fc500c1758034140199d2858392f6/coincurve-17.0.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abbefc9ccb170cb255a31df32457c2e43084b9f37589d0694dacc2dea6ddaf7c"},
+ {url = "https://files.pythonhosted.org/packages/d5/8b/fad420c85fb6772485dbc63388a6593d56b0a6d4c24448bffc9d92ce4490/coincurve-17.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:abbd9d017a7638dc38a3b9bb4851f8801b7818d4e5ac22e0c75e373b3c1dbff0"},
+ {url = "https://files.pythonhosted.org/packages/d7/04/8e69159edcce60a7a60df2afbc85300158a5d756061ebdfede15801efe6b/coincurve-17.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dfd4fab857bcd975edc39111cb5f5c104f138dac2e9ace35ea8434d37bcea3be"},
+ {url = "https://files.pythonhosted.org/packages/db/83/6c42d6bf051034c5b4a01b8e5d4d8aa905af7340fec864f69e38338554fe/coincurve-17.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8c571445b166c714af4f8155e38a894376c16c0431e88963f2fff474a9985d87"},
+ {url = "https://files.pythonhosted.org/packages/e0/f6/7502bdc8b3768441e8769eea5959d3ff586dc5baa7b9840986a6702e5e07/coincurve-17.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d24284d17162569df917a640f19d9654ba3b43cf560ced8864f270da903f73a5"},
+ {url = "https://files.pythonhosted.org/packages/e7/5b/d30131984b8b048a2f778384b109471cbe5e3f0ebf2b3bcc289269600c88/coincurve-17.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:698efdd53e4fe1bbebaee9b75cbc851be617974c1c60098e9145cb7198ae97fb"},
+ {url = "https://files.pythonhosted.org/packages/f2/69/b0e6e1cee2f14f7bd1e1af640a3e2a9649aab19de124dfd8422c9218358f/coincurve-17.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30dd44d1039f1d237aaa2da6d14a455ca88df3bcb00610b41f3253fdca1be97b"},
+ {url = "https://files.pythonhosted.org/packages/f2/c1/dda5434308b3940dfdb84bc24351cf34f2aa846b2a7aaaefdaad528283d5/coincurve-17.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:51e56373ac79f4ec1cfc5da53d72c55f5e5ac28d848b0849ef5e687ace857888"},
+ {url = "https://files.pythonhosted.org/packages/fc/bd/9d0ef98d759005bab56212d9ff21b84dda6f49b9b43a08f0e0416d032410/coincurve-17.0.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a80a207131813b038351c5bdae8f20f5f774bbf53622081f208d040dd2b7528f"},
+]
+"colorama 0.4.6" = [
+ {url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+ {url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
+ {url = "https://test-files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+ {url = "https://test-files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
+]
+"construct 2.10.68" = [
+ {url = "https://files.pythonhosted.org/packages/e0/b7/a4a032e94bcfdff481f2e6fecd472794d9da09f474a2185ed33b2c7cad64/construct-2.10.68.tar.gz", hash = "sha256:7b2a3fd8e5f597a5aa1d614c3bd516fa065db01704c72a1efaaeec6ef23d8b45"},
+]
+"coverage 6.5.0" = [
+ {url = "https://files.pythonhosted.org/packages/02/7a/a45f3958442d50b9a930a62f0dba9ee502521213ebd016203c2890ea212f/coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"},
+ {url = "https://files.pythonhosted.org/packages/05/63/a789b462075395d34f8152229dccf92b25ca73eac05b3f6cd75fa5017095/coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"},
+ {url = "https://files.pythonhosted.org/packages/06/f1/5177428c35f331f118e964f727f79e3a3073a10271a644c8361d3cea8bfd/coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"},
+ {url = "https://files.pythonhosted.org/packages/07/82/79fa21ceca9a9b091eb3c67e27eb648dade27b2c9e1eb23af47232a2a365/coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"},
+ {url = "https://files.pythonhosted.org/packages/0d/ef/8735875a8dc09e1c4e484a5436c8b4148731b70daccc6f203c50b05e7505/coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"},
+ {url = "https://files.pythonhosted.org/packages/10/9e/68e384940179713640743a010ac7f7c813d1087c8730a9c0bdfa73bdffd7/coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"},
+ {url = "https://files.pythonhosted.org/packages/11/9e/7afba355bdabc550b3b2669e3432e71aec87d79400372d7686c09aab0acf/coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"},
+ {url = "https://files.pythonhosted.org/packages/13/f3/c6025ba30f2ce21d20d5332c3819880fe8afdfc008c2e2f9c075c7b67543/coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"},
+ {url = "https://files.pythonhosted.org/packages/15/b0/3639d84ee8a900da0cf6450ab46e22517e4688b6cec0ba8ab6f8166103a2/coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"},
+ {url = "https://files.pythonhosted.org/packages/18/95/27f80dcd8273171b781a19d109aeaed7f13d78ef6d1e2f7134a5826fd1b4/coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"},
+ {url = "https://files.pythonhosted.org/packages/2f/8b/ca3fe3cfbd66d63181f6e6a06b8b494bb327ba8222d2fa628b392b9ad08a/coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"},
+ {url = "https://files.pythonhosted.org/packages/32/40/e2b1ffa42028365e3465d1340e7d390d096fc992dec2c80e4afed6361e83/coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"},
+ {url = "https://files.pythonhosted.org/packages/36/f3/5cbd79cf4cd059c80b59104aca33b8d05af4ad5bf5b1547645ecee716378/coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"},
+ {url = "https://files.pythonhosted.org/packages/3c/7d/d5211ea782b193ab8064b06dc0cc042cf1a4ca9c93a530071459172c550f/coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"},
+ {url = "https://files.pythonhosted.org/packages/40/3b/cd68cb278c4966df00158811ec1e357b9a7d132790c240fc65da57e10013/coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"},
+ {url = "https://files.pythonhosted.org/packages/4b/66/6e588f5dfc93ccedd06d6785c8143f17bb92b89247d50128d8789e9588d0/coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"},
+ {url = "https://files.pythonhosted.org/packages/50/cf/455930004231fa87efe8be06d13512f34e070ddfee8b8bf5a050cdc47ab3/coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"},
+ {url = "https://files.pythonhosted.org/packages/58/2c/213861cec1d9f6451d29c0b1838769b558f6a8c6844b001f6e98c37c4b1b/coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"},
+ {url = "https://files.pythonhosted.org/packages/5c/66/38d1870cb7cf62da49add1d6803fdbcdef632b2808b5c80bcac35b7634d8/coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"},
+ {url = "https://files.pythonhosted.org/packages/61/a6/af54588e2091693026df94b09106ee10dcbcdc8c9b2c3989149e6e44a324/coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"},
+ {url = "https://files.pythonhosted.org/packages/63/e9/f23e8664ec4032d7802a1cf920853196bcbdce7b56408e3efe1b2da08f3c/coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"},
+ {url = "https://files.pythonhosted.org/packages/64/7f/13f5d58f5ca41182d7020af5257c8fd08ddf33921d2a28cf66753571c278/coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"},
+ {url = "https://files.pythonhosted.org/packages/6a/63/8e82513b7e4a1b8d887b4e85c1c2b6c9b754a581b187c0b084f3330ac479/coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"},
+ {url = "https://files.pythonhosted.org/packages/6b/ba/ef67c1e859b8ddd8cafb81199986ff702efcd4ee5d373670a0bc0a293d1f/coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"},
+ {url = "https://files.pythonhosted.org/packages/6b/f2/919f0fdc93d3991ca074894402074d847be8ac1e1d78e7e9e1c371b69a6f/coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"},
+ {url = "https://files.pythonhosted.org/packages/6e/e6/b31a4b2aa9489da59b35ee0ea4259d6fe9b321a1eaa6492f19342d03d53b/coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"},
+ {url = "https://files.pythonhosted.org/packages/76/44/78c1936c2bd9e7705f170d5e413ed34d9d6d7d0324757786627f88df1514/coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"},
+ {url = "https://files.pythonhosted.org/packages/78/98/253ce0cfcc3b352d3072940940ed44a035614f2abe781477f77038d21d9f/coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"},
+ {url = "https://files.pythonhosted.org/packages/85/03/9dcc8b7e269cfeaf5519d433d841a7d78f283c5fb016385d4690e1aedfc1/coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"},
+ {url = "https://files.pythonhosted.org/packages/89/58/5ec19b43a6511288511f64fc4763d95af8403f5926e7e4556e6b29b03a26/coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"},
+ {url = "https://files.pythonhosted.org/packages/89/a2/cbf599e50bb4be416e0408c4cf523c354c51d7da39935461a9687e039481/coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"},
+ {url = "https://files.pythonhosted.org/packages/8f/17/e1d54e0e5a1e82dea1b1d9463dfe347ded58037beda00d326f943a9ef2d4/coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"},
+ {url = "https://files.pythonhosted.org/packages/a1/6b/7efeeffc7559150a705931b2144b936042c561e63ef248f0e0d9f4523d74/coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"},
+ {url = "https://files.pythonhosted.org/packages/a3/a0/4c59586df0511b18f7b59593672a4baadacef8f393024052d59c6222477c/coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"},
+ {url = "https://files.pythonhosted.org/packages/a8/d9/b367c52cb1297414ba967e38fe9b5338ee4700a2d1592fc78532dc9f882f/coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"},
+ {url = "https://files.pythonhosted.org/packages/ac/bc/c9d4fd6b3494d2cc1e26f4b98eb19206b92a59094617ad02d5689ac9d3c4/coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"},
+ {url = "https://files.pythonhosted.org/packages/ae/a3/f45cb5d32de0751863945d22083c15eb8854bb53681b2e792f2066c629b9/coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"},
+ {url = "https://files.pythonhosted.org/packages/b6/08/a88a9f3a11bb2d97c7a6719535a984b009728433838fbc65766488867c80/coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"},
+ {url = "https://files.pythonhosted.org/packages/bd/a0/e263b115808226fdb2658f1887808c06ac3f1b579ef5dda02309e0d54459/coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"},
+ {url = "https://files.pythonhosted.org/packages/c0/18/2a0a9b3c29376ce04ceb7ca2948559dad76409a2c9b3f664756581101e16/coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"},
+ {url = "https://files.pythonhosted.org/packages/c4/8d/5ec7d08f4601d2d792563fe31db5e9322c306848fec1e65ec8885927f739/coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"},
+ {url = "https://files.pythonhosted.org/packages/c8/e8/e712b61abf1282ce3ac9826473ab4b245a4319303cce2e4115a8de1435f2/coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"},
+ {url = "https://files.pythonhosted.org/packages/cd/48/65d314e702b4a7095ea96da0a319a5a377e594354a4a6badde483832bb5a/coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"},
+ {url = "https://files.pythonhosted.org/packages/d6/00/3e12af83af2a46c1fd27b78486f404736934d0288bda4975119611a01cb3/coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"},
+ {url = "https://files.pythonhosted.org/packages/d6/0f/012a7370aaf61123a222b34b657dedc63e03ce2af8d064ac5c5afe14f29c/coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"},
+ {url = "https://files.pythonhosted.org/packages/e5/fb/11982f5faf2990d4d9159e01a12bbf0a7d7873893d4d2e2acec012ad69ae/coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"},
+ {url = "https://files.pythonhosted.org/packages/e6/24/7fe8ededb4060dd8c3f1d86cb624fcb3452f66fbef5051ed7fab126c5c0c/coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"},
+ {url = "https://files.pythonhosted.org/packages/e9/f0/3be949bd129237553714149b1909d34c94137ca4b86e036bc7060431da18/coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"},
+ {url = "https://files.pythonhosted.org/packages/ea/52/c08080405329326a7ff16c0dfdb4feefaa8edd7446413df67386fe1bbfe0/coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"},
+ {url = "https://files.pythonhosted.org/packages/ff/27/339089b558672f04e62d0cd2d49b9280270bad3bc95de24e7eb03deb4638/coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"},
+ {url = "https://test-files.pythonhosted.org/packages/02/7a/a45f3958442d50b9a930a62f0dba9ee502521213ebd016203c2890ea212f/coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"},
+ {url = "https://test-files.pythonhosted.org/packages/05/63/a789b462075395d34f8152229dccf92b25ca73eac05b3f6cd75fa5017095/coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"},
+ {url = "https://test-files.pythonhosted.org/packages/06/f1/5177428c35f331f118e964f727f79e3a3073a10271a644c8361d3cea8bfd/coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"},
+ {url = "https://test-files.pythonhosted.org/packages/07/82/79fa21ceca9a9b091eb3c67e27eb648dade27b2c9e1eb23af47232a2a365/coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"},
+ {url = "https://test-files.pythonhosted.org/packages/0d/ef/8735875a8dc09e1c4e484a5436c8b4148731b70daccc6f203c50b05e7505/coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"},
+ {url = "https://test-files.pythonhosted.org/packages/10/9e/68e384940179713640743a010ac7f7c813d1087c8730a9c0bdfa73bdffd7/coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"},
+ {url = "https://test-files.pythonhosted.org/packages/11/9e/7afba355bdabc550b3b2669e3432e71aec87d79400372d7686c09aab0acf/coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"},
+ {url = "https://test-files.pythonhosted.org/packages/13/f3/c6025ba30f2ce21d20d5332c3819880fe8afdfc008c2e2f9c075c7b67543/coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"},
+ {url = "https://test-files.pythonhosted.org/packages/15/b0/3639d84ee8a900da0cf6450ab46e22517e4688b6cec0ba8ab6f8166103a2/coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"},
+ {url = "https://test-files.pythonhosted.org/packages/18/95/27f80dcd8273171b781a19d109aeaed7f13d78ef6d1e2f7134a5826fd1b4/coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"},
+ {url = "https://test-files.pythonhosted.org/packages/2f/8b/ca3fe3cfbd66d63181f6e6a06b8b494bb327ba8222d2fa628b392b9ad08a/coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"},
+ {url = "https://test-files.pythonhosted.org/packages/32/40/e2b1ffa42028365e3465d1340e7d390d096fc992dec2c80e4afed6361e83/coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"},
+ {url = "https://test-files.pythonhosted.org/packages/36/f3/5cbd79cf4cd059c80b59104aca33b8d05af4ad5bf5b1547645ecee716378/coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"},
+ {url = "https://test-files.pythonhosted.org/packages/3c/7d/d5211ea782b193ab8064b06dc0cc042cf1a4ca9c93a530071459172c550f/coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"},
+ {url = "https://test-files.pythonhosted.org/packages/40/3b/cd68cb278c4966df00158811ec1e357b9a7d132790c240fc65da57e10013/coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"},
+ {url = "https://test-files.pythonhosted.org/packages/4b/66/6e588f5dfc93ccedd06d6785c8143f17bb92b89247d50128d8789e9588d0/coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"},
+ {url = "https://test-files.pythonhosted.org/packages/50/cf/455930004231fa87efe8be06d13512f34e070ddfee8b8bf5a050cdc47ab3/coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"},
+ {url = "https://test-files.pythonhosted.org/packages/58/2c/213861cec1d9f6451d29c0b1838769b558f6a8c6844b001f6e98c37c4b1b/coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"},
+ {url = "https://test-files.pythonhosted.org/packages/5c/66/38d1870cb7cf62da49add1d6803fdbcdef632b2808b5c80bcac35b7634d8/coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"},
+ {url = "https://test-files.pythonhosted.org/packages/61/a6/af54588e2091693026df94b09106ee10dcbcdc8c9b2c3989149e6e44a324/coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"},
+ {url = "https://test-files.pythonhosted.org/packages/63/e9/f23e8664ec4032d7802a1cf920853196bcbdce7b56408e3efe1b2da08f3c/coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"},
+ {url = "https://test-files.pythonhosted.org/packages/64/7f/13f5d58f5ca41182d7020af5257c8fd08ddf33921d2a28cf66753571c278/coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"},
+ {url = "https://test-files.pythonhosted.org/packages/6a/63/8e82513b7e4a1b8d887b4e85c1c2b6c9b754a581b187c0b084f3330ac479/coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"},
+ {url = "https://test-files.pythonhosted.org/packages/6b/ba/ef67c1e859b8ddd8cafb81199986ff702efcd4ee5d373670a0bc0a293d1f/coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"},
+ {url = "https://test-files.pythonhosted.org/packages/6b/f2/919f0fdc93d3991ca074894402074d847be8ac1e1d78e7e9e1c371b69a6f/coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"},
+ {url = "https://test-files.pythonhosted.org/packages/6e/e6/b31a4b2aa9489da59b35ee0ea4259d6fe9b321a1eaa6492f19342d03d53b/coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"},
+ {url = "https://test-files.pythonhosted.org/packages/76/44/78c1936c2bd9e7705f170d5e413ed34d9d6d7d0324757786627f88df1514/coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"},
+ {url = "https://test-files.pythonhosted.org/packages/78/98/253ce0cfcc3b352d3072940940ed44a035614f2abe781477f77038d21d9f/coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"},
+ {url = "https://test-files.pythonhosted.org/packages/85/03/9dcc8b7e269cfeaf5519d433d841a7d78f283c5fb016385d4690e1aedfc1/coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"},
+ {url = "https://test-files.pythonhosted.org/packages/89/58/5ec19b43a6511288511f64fc4763d95af8403f5926e7e4556e6b29b03a26/coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"},
+ {url = "https://test-files.pythonhosted.org/packages/89/a2/cbf599e50bb4be416e0408c4cf523c354c51d7da39935461a9687e039481/coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"},
+ {url = "https://test-files.pythonhosted.org/packages/8f/17/e1d54e0e5a1e82dea1b1d9463dfe347ded58037beda00d326f943a9ef2d4/coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"},
+ {url = "https://test-files.pythonhosted.org/packages/a1/6b/7efeeffc7559150a705931b2144b936042c561e63ef248f0e0d9f4523d74/coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"},
+ {url = "https://test-files.pythonhosted.org/packages/a3/a0/4c59586df0511b18f7b59593672a4baadacef8f393024052d59c6222477c/coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"},
+ {url = "https://test-files.pythonhosted.org/packages/a8/d9/b367c52cb1297414ba967e38fe9b5338ee4700a2d1592fc78532dc9f882f/coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"},
+ {url = "https://test-files.pythonhosted.org/packages/ac/bc/c9d4fd6b3494d2cc1e26f4b98eb19206b92a59094617ad02d5689ac9d3c4/coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"},
+ {url = "https://test-files.pythonhosted.org/packages/ae/a3/f45cb5d32de0751863945d22083c15eb8854bb53681b2e792f2066c629b9/coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"},
+ {url = "https://test-files.pythonhosted.org/packages/b6/08/a88a9f3a11bb2d97c7a6719535a984b009728433838fbc65766488867c80/coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"},
+ {url = "https://test-files.pythonhosted.org/packages/bd/a0/e263b115808226fdb2658f1887808c06ac3f1b579ef5dda02309e0d54459/coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"},
+ {url = "https://test-files.pythonhosted.org/packages/c0/18/2a0a9b3c29376ce04ceb7ca2948559dad76409a2c9b3f664756581101e16/coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"},
+ {url = "https://test-files.pythonhosted.org/packages/c4/8d/5ec7d08f4601d2d792563fe31db5e9322c306848fec1e65ec8885927f739/coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"},
+ {url = "https://test-files.pythonhosted.org/packages/c8/e8/e712b61abf1282ce3ac9826473ab4b245a4319303cce2e4115a8de1435f2/coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"},
+ {url = "https://test-files.pythonhosted.org/packages/cd/48/65d314e702b4a7095ea96da0a319a5a377e594354a4a6badde483832bb5a/coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"},
+ {url = "https://test-files.pythonhosted.org/packages/d6/00/3e12af83af2a46c1fd27b78486f404736934d0288bda4975119611a01cb3/coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"},
+ {url = "https://test-files.pythonhosted.org/packages/d6/0f/012a7370aaf61123a222b34b657dedc63e03ce2af8d064ac5c5afe14f29c/coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"},
+ {url = "https://test-files.pythonhosted.org/packages/e5/fb/11982f5faf2990d4d9159e01a12bbf0a7d7873893d4d2e2acec012ad69ae/coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"},
+ {url = "https://test-files.pythonhosted.org/packages/e6/24/7fe8ededb4060dd8c3f1d86cb624fcb3452f66fbef5051ed7fab126c5c0c/coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"},
+ {url = "https://test-files.pythonhosted.org/packages/e9/f0/3be949bd129237553714149b1909d34c94137ca4b86e036bc7060431da18/coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"},
+ {url = "https://test-files.pythonhosted.org/packages/ea/52/c08080405329326a7ff16c0dfdb4feefaa8edd7446413df67386fe1bbfe0/coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"},
+ {url = "https://test-files.pythonhosted.org/packages/ff/27/339089b558672f04e62d0cd2d49b9280270bad3bc95de24e7eb03deb4638/coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"},
+]
+"crcmod 1.7" = [
+ {url = "https://files.pythonhosted.org/packages/6b/b0/e595ce2a2527e169c3bcd6c33d2473c1918e0b7f6826a043ca1245dd4e5b/crcmod-1.7.tar.gz", hash = "sha256:dc7051a0db5f2bd48665a990d3ec1cc305a466a77358ca4492826f41f283601e"},
+]
+"ecdsa 0.18.0" = [
+ {url = "https://files.pythonhosted.org/packages/09/d4/4f05f5d16a4863b30ba96c23b23e942da8889abfa1cdbabf2a0df12a4532/ecdsa-0.18.0-py2.py3-none-any.whl", hash = "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd"},
+ {url = "https://files.pythonhosted.org/packages/ff/7b/ba6547a76c468a0d22de93e89ae60d9561ec911f59532907e72b0d8bc0f1/ecdsa-0.18.0.tar.gz", hash = "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49"},
+]
+"ed25519-blake2b 1.4" = [
+ {url = "https://files.pythonhosted.org/packages/1d/f1/035bafb5b2ed5b379abd4bf26ebad6e9fed73e21756971cefe027bd55ec2/ed25519-blake2b-1.4.tar.gz", hash = "sha256:d1a1cb9032ec307ce95b41c619440fd4d3fcecc18f224035cc7d6dc7a7d8ef40"},
+ {url = "https://test-files.pythonhosted.org/packages/1e/6a/7c1ef79662d40e2e031e5b1a155c0d6f0dd6029d3747fc9f3d5b27c49740/ed25519-blake2b-1.4.tar.gz", hash = "sha256:0ef98ed9ebec59f312a447a0693c2d8bcc8a4d011ca5bb7d09104d358669b122"},
+]
+"exceptiongroup 1.0.4" = [
+ {url = "https://files.pythonhosted.org/packages/ce/2e/9a327cc0d2d674ee2d570ee30119755af772094edba86d721dda94404d1a/exceptiongroup-1.0.4-py3-none-any.whl", hash = "sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828"},
+ {url = "https://files.pythonhosted.org/packages/fa/e1/4f89a2608978a78876a76e69e82fa1fc5ce3fb346297eed2a51dd3df2dcf/exceptiongroup-1.0.4.tar.gz", hash = "sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec"},
+]
+"flask 2.2.2" = [
+ {url = "https://files.pythonhosted.org/packages/0f/43/15f4f9ab225b0b25352412e8daa3d0e3d135fcf5e127070c74c3632c8b4c/Flask-2.2.2-py3-none-any.whl", hash = "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526"},
+ {url = "https://files.pythonhosted.org/packages/69/b6/53cfa30eed5aa7343daff36622843688ba8c6fe9829bb2b92e193ab1163f/Flask-2.2.2.tar.gz", hash = "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b"},
+ {url = "https://test-files.pythonhosted.org/packages/0f/43/15f4f9ab225b0b25352412e8daa3d0e3d135fcf5e127070c74c3632c8b4c/Flask-2.2.2-py3-none-any.whl", hash = "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526"},
+ {url = "https://test-files.pythonhosted.org/packages/69/b6/53cfa30eed5aa7343daff36622843688ba8c6fe9829bb2b92e193ab1163f/Flask-2.2.2.tar.gz", hash = "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b"},
+]
+"flask-restful 0.3.9" = [
+ {url = "https://files.pythonhosted.org/packages/5c/50/4892719b13abd401f40a69359c3d859d0ea76bf78e66db058d6c06a95b01/Flask-RESTful-0.3.9.tar.gz", hash = "sha256:ccec650b835d48192138c85329ae03735e6ced58e9b2d9c2146d6c84c06fa53e"},
+ {url = "https://files.pythonhosted.org/packages/a9/02/7e21a73564fe0d9d1a3a4ff478dfc407815c4e2fa4e5121bcfc646ba5d15/Flask_RESTful-0.3.9-py2.py3-none-any.whl", hash = "sha256:4970c49b6488e46c520b325f54833374dc2b98e211f1b272bd4b0c516232afe2"},
+]
+"idna 3.4" = [
+ {url = "https://files.pythonhosted.org/packages/8b/e1/43beb3d38dba6cb420cefa297822eac205a277ab43e5ba5d5c46faf96438/idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
+ {url = "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
+]
+"iniconfig 1.1.1" = [
+ {url = "https://files.pythonhosted.org/packages/23/a2/97899f6bd0e873fed3a7e67ae8d3a08b21799430fb4da15cfedf10d6e2c2/iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
+ {url = "https://files.pythonhosted.org/packages/9b/dd/b3c12c6d707058fa947864b67f0c4e0c39ef8610988d7baea9578f3c48f3/iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
+ {url = "https://test-files.pythonhosted.org/packages/75/f0/6126fa58c2647daefb619a9ea449c53b5b61acd44f6d2693beae754977e0/iniconfig-1.1.1.tar.gz", hash = "sha256:714dfcaf550420126ed7e66bb1cbc893752af388742828f294c21f847d78b88a"},
+ {url = "https://test-files.pythonhosted.org/packages/76/83/2373f13f03040a54c77272adac18956b8202a50b60e8221449df00d19e87/iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:8cd395a2a89caee27b08d7879c1515fa9d93e0c477a5b38c9893787fc5e51896"},
+]
+"itsdangerous 2.1.2" = [
+ {url = "https://files.pythonhosted.org/packages/68/5f/447e04e828f47465eeab35b5d408b7ebaaaee207f48b7136c5a7267a30ae/itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"},
+ {url = "https://files.pythonhosted.org/packages/7f/a1/d3fb83e7a61fa0c0d3d08ad0a94ddbeff3731c05212617dff3a94e097f08/itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"},
+ {url = "https://test-files.pythonhosted.org/packages/68/5f/447e04e828f47465eeab35b5d408b7ebaaaee207f48b7136c5a7267a30ae/itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"},
+ {url = "https://test-files.pythonhosted.org/packages/7f/a1/d3fb83e7a61fa0c0d3d08ad0a94ddbeff3731c05212617dff3a94e097f08/itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"},
+]
+"jinja2 3.1.2" = [
+ {url = "https://files.pythonhosted.org/packages/7a/ff/75c28576a1d900e87eb6335b063fab47a8ef3c8b4d88524c4bf78f670cce/Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
+ {url = "https://files.pythonhosted.org/packages/bc/c3/f068337a370801f372f2f8f6bad74a5c140f6fda3d9de154052708dd3c65/Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
+ {url = "https://test-files.pythonhosted.org/packages/7a/ff/75c28576a1d900e87eb6335b063fab47a8ef3c8b4d88524c4bf78f670cce/Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
+ {url = "https://test-files.pythonhosted.org/packages/bc/c3/f068337a370801f372f2f8f6bad74a5c140f6fda3d9de154052708dd3c65/Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
+]
+"jsonschema 3.2.0" = [
+ {url = "https://files.pythonhosted.org/packages/69/11/a69e2a3c01b324a77d3a7c0570faa372e8448b666300c4117a516f8b1212/jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"},
+ {url = "https://files.pythonhosted.org/packages/c5/8f/51e89ce52a085483359217bc72cdbf6e75ee595d5b1d4b5ade40c7e018b8/jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"},
+]
+"markupsafe 2.1.1" = [
+ {url = "https://files.pythonhosted.org/packages/06/7f/d5e46d7464360b6ac39c5b0b604770dba937e3d7cab485d2f3298454717b/MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"},
+ {url = "https://files.pythonhosted.org/packages/0f/53/b14de4ede9c2bd76d28e7911033b065ac42896f1cfb258d3ff65cf0332d2/MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"},
+ {url = "https://files.pythonhosted.org/packages/18/a6/913b1d80fe93f7c3aa79206544b98841616c3eaa7790f37bdfb9fc13311e/MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"},
+ {url = "https://files.pythonhosted.org/packages/1b/b3/93411f10caaccc6fc9c53bbdae4f6d26997248ae574e2f0c90e912b67f73/MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"},
+ {url = "https://files.pythonhosted.org/packages/1d/97/2288fe498044284f39ab8950703e88abbac2abbdf65524d576157af70556/MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"},
+ {url = "https://files.pythonhosted.org/packages/25/c4/a75659da6d6b03d2d8ef296b2a8bc73e8c5b1533ee31569a958a292f0929/MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"},
+ {url = "https://files.pythonhosted.org/packages/26/03/2c11ba1a8b2327adea3f59f1c9c9ee9c59e86023925f929e63c4f028b10a/MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"},
+ {url = "https://files.pythonhosted.org/packages/2c/81/91062a81ac8a18f557f12e2618475b53878755c016c9914c8aa207155c4e/MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"},
+ {url = "https://files.pythonhosted.org/packages/3a/fc/dccc18170917f2cc2a5b77aad97f5f27d992ef0f2b9fb9e334ee71bf5301/MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"},
+ {url = "https://files.pythonhosted.org/packages/3c/d3/c7ab031b14ae4e83214949acee957a8fcf6a992698edff039ee1e77eb4e1/MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"},
+ {url = "https://files.pythonhosted.org/packages/3d/4b/15e5b9d40c4b58e97ebcb8ed5845a215fa5b7cf49a7f1cc7908f8db9cf46/MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"},
+ {url = "https://files.pythonhosted.org/packages/3f/38/f422a81b41bdac48f1d19c45f6e203c04bc45d2c35505873fdecdddee1ec/MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"},
+ {url = "https://files.pythonhosted.org/packages/48/a9/cf226ea201542a724b113bac70dd0dfb72106da3621120c525c8eafadac2/MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"},
+ {url = "https://files.pythonhosted.org/packages/5c/1a/ac3a2b2a4ef1196c15dd8a143fc28eddeb6e6871d6d1de64dc44ef7f59b6/MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"},
+ {url = "https://files.pythonhosted.org/packages/5e/3d/0a7df21deca52e20de81f8a895ac29df68944588c0030be9aa1e6c07877c/MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"},
+ {url = "https://files.pythonhosted.org/packages/68/b5/b3aafabe7e1f71aa64ffe32fd8c767fd7db1bb304d339d8df6f2fdd2543c/MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"},
+ {url = "https://files.pythonhosted.org/packages/69/60/08791e4a971ea976f0fd58fb916d76de7c962dc8e26430564258820ac21f/MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"},
+ {url = "https://files.pythonhosted.org/packages/6c/44/cd459988fe29cb82f0482fe6b6c47ec17ae700a500634edd876075d5e1ee/MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"},
+ {url = "https://files.pythonhosted.org/packages/71/dc/41cbfe0d9aefdf14226dbf4eccfd0079a0e297809a17c5b902c9a7a3cc9a/MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"},
+ {url = "https://files.pythonhosted.org/packages/73/68/628f6dbbf5088723a2b939d97c0a2e059d0cc654ce92a6fac5c7959edaff/MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"},
+ {url = "https://files.pythonhosted.org/packages/7f/d7/a0ee1e3a608ca2f80c66c43c699ab063b4b8979c51f8299229b1960f6860/MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"},
+ {url = "https://files.pythonhosted.org/packages/82/3d/523e40c45dc1f53b35e60c6e8563dec523f7b6c113f823d5e123dd431631/MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"},
+ {url = "https://files.pythonhosted.org/packages/87/31/ab37f60fde001c02ac115da6f66a2d934d37407f257ad8e15ed69093c7fb/MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"},
+ {url = "https://files.pythonhosted.org/packages/8c/96/7e608e1a942232cb8c81ca24093e71e07e2bacbeb2dad62a0f82da28ed54/MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"},
+ {url = "https://files.pythonhosted.org/packages/92/7c/3c33294e506eafa7f1c40dd283089a45652ea0f073fc0ce24419d46bfe4b/MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"},
+ {url = "https://files.pythonhosted.org/packages/9e/82/2e089c6f34e77c073aa5a67040d368aac0dfb9b8ccbb46d381452c26fc33/MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"},
+ {url = "https://files.pythonhosted.org/packages/9f/83/b221ce5a0224f409b9f02b0dc6cb0b921c46033f4870d64fa3e8a96af701/MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"},
+ {url = "https://files.pythonhosted.org/packages/a3/47/9dcc08eff8ab94f1e50f59f9cd322b710ef5db7e8590fdd8df924406fc9c/MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"},
+ {url = "https://files.pythonhosted.org/packages/ad/fa/292a72cddad41e3c06227b446a0af53ff642a40755fc5bd695f439c35ba8/MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"},
+ {url = "https://files.pythonhosted.org/packages/ba/16/3627f852d8a846c0fc52ad1beac6e27894a8344cc2c26036db51acb82c3e/MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"},
+ {url = "https://files.pythonhosted.org/packages/ba/a9/6291d3fdaf0ecac5fbafe462258c5174f11fd752076ba05c2c022ee64f77/MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"},
+ {url = "https://files.pythonhosted.org/packages/be/d8/5ab7f07d8f60155c4f12b4b2dca785355b8ee7e16b2d3f00c3830add5f10/MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"},
+ {url = "https://files.pythonhosted.org/packages/d0/1f/9677deb5b2768ca503e5ed8464a28f47a854d415b9d1b84445efa8363ca6/MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"},
+ {url = "https://files.pythonhosted.org/packages/d3/4f/9ea1c0a7796f7f81371b40d32aa31766b76fbdba316abf888897042e6e0f/MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"},
+ {url = "https://files.pythonhosted.org/packages/d9/60/94e9de017674f88a514804e2924bdede9a642aba179d2045214719d6ec76/MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"},
+ {url = "https://files.pythonhosted.org/packages/df/06/c515c5bc43b90462e753bc768e6798193c6520c9c7eb2054c7466779a9db/MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"},
+ {url = "https://files.pythonhosted.org/packages/f9/f8/13ffc95bf8a8c98d611b9f9fa5aa88625b9a82fce528e58f1aafba14946b/MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"},
+ {url = "https://files.pythonhosted.org/packages/fc/e4/78c7607352dd574d524daad079f855757d406d36b919b1864a5a07978390/MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"},
+ {url = "https://files.pythonhosted.org/packages/fd/f4/524d2e8f5a3727cf309c2b7df7c732038375322df1376c9e9ef3aa92fcaf/MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"},
+ {url = "https://files.pythonhosted.org/packages/ff/3a/42262a3aa6415befee33b275b31afbcef4f7f8d2f4380061b226c692ee2a/MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"},
+ {url = "https://test-files.pythonhosted.org/packages/06/7f/d5e46d7464360b6ac39c5b0b604770dba937e3d7cab485d2f3298454717b/MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"},
+ {url = "https://test-files.pythonhosted.org/packages/0f/53/b14de4ede9c2bd76d28e7911033b065ac42896f1cfb258d3ff65cf0332d2/MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"},
+ {url = "https://test-files.pythonhosted.org/packages/18/a6/913b1d80fe93f7c3aa79206544b98841616c3eaa7790f37bdfb9fc13311e/MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"},
+ {url = "https://test-files.pythonhosted.org/packages/1b/b3/93411f10caaccc6fc9c53bbdae4f6d26997248ae574e2f0c90e912b67f73/MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"},
+ {url = "https://test-files.pythonhosted.org/packages/1d/97/2288fe498044284f39ab8950703e88abbac2abbdf65524d576157af70556/MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"},
+ {url = "https://test-files.pythonhosted.org/packages/25/c4/a75659da6d6b03d2d8ef296b2a8bc73e8c5b1533ee31569a958a292f0929/MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"},
+ {url = "https://test-files.pythonhosted.org/packages/26/03/2c11ba1a8b2327adea3f59f1c9c9ee9c59e86023925f929e63c4f028b10a/MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"},
+ {url = "https://test-files.pythonhosted.org/packages/2c/81/91062a81ac8a18f557f12e2618475b53878755c016c9914c8aa207155c4e/MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"},
+ {url = "https://test-files.pythonhosted.org/packages/3a/fc/dccc18170917f2cc2a5b77aad97f5f27d992ef0f2b9fb9e334ee71bf5301/MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"},
+ {url = "https://test-files.pythonhosted.org/packages/3c/d3/c7ab031b14ae4e83214949acee957a8fcf6a992698edff039ee1e77eb4e1/MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"},
+ {url = "https://test-files.pythonhosted.org/packages/3d/4b/15e5b9d40c4b58e97ebcb8ed5845a215fa5b7cf49a7f1cc7908f8db9cf46/MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"},
+ {url = "https://test-files.pythonhosted.org/packages/3f/38/f422a81b41bdac48f1d19c45f6e203c04bc45d2c35505873fdecdddee1ec/MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"},
+ {url = "https://test-files.pythonhosted.org/packages/48/a9/cf226ea201542a724b113bac70dd0dfb72106da3621120c525c8eafadac2/MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"},
+ {url = "https://test-files.pythonhosted.org/packages/5c/1a/ac3a2b2a4ef1196c15dd8a143fc28eddeb6e6871d6d1de64dc44ef7f59b6/MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"},
+ {url = "https://test-files.pythonhosted.org/packages/5e/3d/0a7df21deca52e20de81f8a895ac29df68944588c0030be9aa1e6c07877c/MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"},
+ {url = "https://test-files.pythonhosted.org/packages/68/b5/b3aafabe7e1f71aa64ffe32fd8c767fd7db1bb304d339d8df6f2fdd2543c/MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"},
+ {url = "https://test-files.pythonhosted.org/packages/69/60/08791e4a971ea976f0fd58fb916d76de7c962dc8e26430564258820ac21f/MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"},
+ {url = "https://test-files.pythonhosted.org/packages/6c/44/cd459988fe29cb82f0482fe6b6c47ec17ae700a500634edd876075d5e1ee/MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"},
+ {url = "https://test-files.pythonhosted.org/packages/71/dc/41cbfe0d9aefdf14226dbf4eccfd0079a0e297809a17c5b902c9a7a3cc9a/MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"},
+ {url = "https://test-files.pythonhosted.org/packages/73/68/628f6dbbf5088723a2b939d97c0a2e059d0cc654ce92a6fac5c7959edaff/MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"},
+ {url = "https://test-files.pythonhosted.org/packages/7f/d7/a0ee1e3a608ca2f80c66c43c699ab063b4b8979c51f8299229b1960f6860/MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"},
+ {url = "https://test-files.pythonhosted.org/packages/82/3d/523e40c45dc1f53b35e60c6e8563dec523f7b6c113f823d5e123dd431631/MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"},
+ {url = "https://test-files.pythonhosted.org/packages/87/31/ab37f60fde001c02ac115da6f66a2d934d37407f257ad8e15ed69093c7fb/MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"},
+ {url = "https://test-files.pythonhosted.org/packages/8c/96/7e608e1a942232cb8c81ca24093e71e07e2bacbeb2dad62a0f82da28ed54/MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"},
+ {url = "https://test-files.pythonhosted.org/packages/92/7c/3c33294e506eafa7f1c40dd283089a45652ea0f073fc0ce24419d46bfe4b/MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"},
+ {url = "https://test-files.pythonhosted.org/packages/9e/82/2e089c6f34e77c073aa5a67040d368aac0dfb9b8ccbb46d381452c26fc33/MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"},
+ {url = "https://test-files.pythonhosted.org/packages/9f/83/b221ce5a0224f409b9f02b0dc6cb0b921c46033f4870d64fa3e8a96af701/MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"},
+ {url = "https://test-files.pythonhosted.org/packages/a3/47/9dcc08eff8ab94f1e50f59f9cd322b710ef5db7e8590fdd8df924406fc9c/MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"},
+ {url = "https://test-files.pythonhosted.org/packages/ad/fa/292a72cddad41e3c06227b446a0af53ff642a40755fc5bd695f439c35ba8/MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"},
+ {url = "https://test-files.pythonhosted.org/packages/ba/16/3627f852d8a846c0fc52ad1beac6e27894a8344cc2c26036db51acb82c3e/MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"},
+ {url = "https://test-files.pythonhosted.org/packages/ba/a9/6291d3fdaf0ecac5fbafe462258c5174f11fd752076ba05c2c022ee64f77/MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"},
+ {url = "https://test-files.pythonhosted.org/packages/be/d8/5ab7f07d8f60155c4f12b4b2dca785355b8ee7e16b2d3f00c3830add5f10/MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"},
+ {url = "https://test-files.pythonhosted.org/packages/d0/1f/9677deb5b2768ca503e5ed8464a28f47a854d415b9d1b84445efa8363ca6/MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"},
+ {url = "https://test-files.pythonhosted.org/packages/d3/4f/9ea1c0a7796f7f81371b40d32aa31766b76fbdba316abf888897042e6e0f/MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"},
+ {url = "https://test-files.pythonhosted.org/packages/d9/60/94e9de017674f88a514804e2924bdede9a642aba179d2045214719d6ec76/MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"},
+ {url = "https://test-files.pythonhosted.org/packages/df/06/c515c5bc43b90462e753bc768e6798193c6520c9c7eb2054c7466779a9db/MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"},
+ {url = "https://test-files.pythonhosted.org/packages/f9/f8/13ffc95bf8a8c98d611b9f9fa5aa88625b9a82fce528e58f1aafba14946b/MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"},
+ {url = "https://test-files.pythonhosted.org/packages/fc/e4/78c7607352dd574d524daad079f855757d406d36b919b1864a5a07978390/MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"},
+ {url = "https://test-files.pythonhosted.org/packages/fd/f4/524d2e8f5a3727cf309c2b7df7c732038375322df1376c9e9ef3aa92fcaf/MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"},
+ {url = "https://test-files.pythonhosted.org/packages/ff/3a/42262a3aa6415befee33b275b31afbcef4f7f8d2f4380061b226c692ee2a/MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"},
+]
+"mnemonic 0.20" = [
+ {url = "https://files.pythonhosted.org/packages/ae/95/3e07c33ffb26f5823b45a1c30db8acea44763198c2bd393e07e884f3295f/mnemonic-0.20-py3-none-any.whl", hash = "sha256:acd2168872d0379e7a10873bb3e12bf6c91b35de758135c4fbd1015ef18fafc5"},
+ {url = "https://files.pythonhosted.org/packages/f8/8d/d4dc2b2bddfeb57cab4404a41749b577f578f71140ab754da9afa8f5c599/mnemonic-0.20.tar.gz", hash = "sha256:7c6fb5639d779388027a77944680aee4870f0fcd09b1e42a5525ee2ce4c625f6"},
+]
+"mypy-extensions 0.4.3" = [
+ {url = "https://files.pythonhosted.org/packages/5c/eb/975c7c080f3223a5cdaff09612f3a5221e4ba534f7039db34c35d95fa6a5/mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
+ {url = "https://files.pythonhosted.org/packages/63/60/0582ce2eaced55f65a4406fc97beba256de4b7a95a0034c6576458c6519f/mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
+]
+"mypy-protobuf 3.3.0" = [
+ {url = "https://files.pythonhosted.org/packages/9d/59/48f1df10f87cf87d0638dd38f54549e98cb43fcf7ce0ab6c159816e85f23/mypy-protobuf-3.3.0.tar.gz", hash = "sha256:24f3b0aecb06656e983f58e07c732a90577b9d7af3e1066fc2b663bbf0370248"},
+ {url = "https://files.pythonhosted.org/packages/d3/7b/b00434b3f508eb4bc637b83c2238cacaf1ce3cc5e540a2b76f5f99302446/mypy_protobuf-3.3.0-py3-none-any.whl", hash = "sha256:15604f6943b16c05db646903261e3b3e775cf7f7990b7c37b03d043a907b650d"},
+ {url = "https://test-files.pythonhosted.org/packages/9d/59/48f1df10f87cf87d0638dd38f54549e98cb43fcf7ce0ab6c159816e85f23/mypy-protobuf-3.3.0.tar.gz", hash = "sha256:24f3b0aecb06656e983f58e07c732a90577b9d7af3e1066fc2b663bbf0370248"},
+ {url = "https://test-files.pythonhosted.org/packages/d3/7b/b00434b3f508eb4bc637b83c2238cacaf1ce3cc5e540a2b76f5f99302446/mypy_protobuf-3.3.0-py3-none-any.whl", hash = "sha256:15604f6943b16c05db646903261e3b3e775cf7f7990b7c37b03d043a907b650d"},
+]
+"packaging 21.3" = [
+ {url = "https://files.pythonhosted.org/packages/05/8e/8de486cbd03baba4deef4142bd643a3e7bbe954a784dc1bb17142572d127/packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
+ {url = "https://files.pythonhosted.org/packages/df/9e/d1a7217f69310c1db8fdf8ab396229f55a699ce34a203691794c5d1cad0c/packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
+]
+"pathspec 0.10.2" = [
+ {url = "https://files.pythonhosted.org/packages/42/79/94b21d5fabb97749ca94590315abe150a750483c87add8543781bcb6cd26/pathspec-0.10.2-py3-none-any.whl", hash = "sha256:88c2606f2c1e818b978540f73ecc908e13999c6c3a383daf3705652ae79807a5"},
+ {url = "https://files.pythonhosted.org/packages/a2/29/959c72e1a6c3c25eaa46b9bfcc7fd401f65af83163d4796af09272c83c8a/pathspec-0.10.2.tar.gz", hash = "sha256:8f6bf73e5758fd365ef5d58ce09ac7c27d2833a8d7da51712eac6e27e35141b0"},
+]
+"pillow 9.3.0" = [
+ {url = "https://files.pythonhosted.org/packages/00/73/000575ca7b7635ecd4075e71925b71f2648300d725b6c9a1f969fe2d5a87/Pillow-9.3.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:772a91fc0e03eaf922c63badeca75e91baa80fe2f5f87bdaed4280662aad25c9"},
+ {url = "https://files.pythonhosted.org/packages/06/2f/c17a2d559009b875f7da804a3a4e0f47baca6df2dfedb29a1df641c51e1a/Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12ce4932caf2ddf3e41d17fc9c02d67126935a44b86df6a206cf0d7161548627"},
+ {url = "https://files.pythonhosted.org/packages/0d/87/f696d8464c949c5a7ac133c8b7297a4243e0319fd29ae3a90b4ac90f0a5b/Pillow-9.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7db8b751ad307d7cf238f02101e8e36a128a6cb199326e867d1398067381bff4"},
+ {url = "https://files.pythonhosted.org/packages/15/13/232be66adb8482f0636b3f3e254d9e18d7093bd1848b3610ffaf72717924/Pillow-9.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:829f97c8e258593b9daa80638aee3789b7df9da5cf1336035016d76f03b8860c"},
+ {url = "https://files.pythonhosted.org/packages/16/11/da8d395299ca166aa56d9436e26fe8440e5443471de16ccd9a1d06f5993a/Pillow-9.3.0.tar.gz", hash = "sha256:c935a22a557a560108d780f9a0fc426dd7459940dc54faa49d83249c8d3e760f"},
+ {url = "https://files.pythonhosted.org/packages/1d/15/054644ecbed79d3af017a56eaabd13bf712b21c3a667b8a67a335174f4b7/Pillow-9.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:89dca0ce00a2b49024df6325925555d406b14aa3efc2f752dbb5940c52c56b11"},
+ {url = "https://files.pythonhosted.org/packages/23/b3/20f97e1bacd02b89953b50b2502be1b10e01d36a0618fb197e4a055e50f4/Pillow-9.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68943d632f1f9e3dce98908e873b3a090f6cba1cbb1b892a9e8d97c938871fbe"},
+ {url = "https://files.pythonhosted.org/packages/26/d6/9355d59a2ee9406e4d5129ff0ff99835e7f6adb6133815fab2099e1879b5/Pillow-9.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:0b7257127d646ff8676ec8a15520013a698d1fdc48bc2a79ba4e53df792526f2"},
+ {url = "https://files.pythonhosted.org/packages/2c/3d/36cd1f6c04ab30ab7e6bc8d17a4337fc29fa5b35ebd4c520d916ce7e39e6/Pillow-9.3.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:b03ae6f1a1878233ac620c98f3459f79fd77c7e3c2b20d460284e1fb370557d4"},
+ {url = "https://files.pythonhosted.org/packages/2c/74/109e3d1fd2847c19c556fe4ce9b3f4aac2147b56c7b5d0924ae9586d2a47/Pillow-9.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a65733d103311331875c1dca05cb4606997fd33d6acfed695b1232ba1df193"},
+ {url = "https://files.pythonhosted.org/packages/2e/c8/03495501bb9beabf34979781b14cc625f39cbd8ff5310a93a85e45987590/Pillow-9.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4390e9ce199fc1951fcfa65795f239a8a4944117b5935a9317fb320e7767b40f"},
+ {url = "https://files.pythonhosted.org/packages/2f/73/ec6b3e3f6b311cf1468eafc92a890f690a2cacac0cfd0f1bcc2b891d1334/Pillow-9.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af0372acb5d3598f36ec0914deed2a63f6bcdb7b606da04dc19a88d31bf0c05b"},
+ {url = "https://files.pythonhosted.org/packages/39/5e/585dfbe0eb4660ea3ee082289625f94e402239282458c4bf78ecc84fbb93/Pillow-9.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40e1ce476a7804b0fb74bcfa80b0a2206ea6a882938eaba917f7a0f004b42502"},
+ {url = "https://files.pythonhosted.org/packages/40/57/c8695a77561a83bd39eba30daf4d894b0b910aad55e2b60f0ef1b1b5205c/Pillow-9.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:9f47eabcd2ded7698106b05c2c338672d16a6f2a485e74481f524e2a23c2794b"},
+ {url = "https://files.pythonhosted.org/packages/41/e9/7bae68c360de8d1e7e32aeab252c6dc9336c0d779c9ad5e24ce475d523fd/Pillow-9.3.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ab388aaa3f6ce52ac1cb8e122c4bd46657c15905904b3120a6248b5b8b0bc228"},
+ {url = "https://files.pythonhosted.org/packages/47/b2/6e5fc952713bfee71c5e25e7917b18207b6f445d81746008b14d9c80a91b/Pillow-9.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:15c42fb9dea42465dfd902fb0ecf584b8848ceb28b41ee2b58f866411be33f07"},
+ {url = "https://files.pythonhosted.org/packages/48/92/820b43fcb333fe7f4cf726ed3c0cfcfd062f65b8fd23c0e6972a6107f440/Pillow-9.3.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b59430236b8e58840a0dfb4099a0e8717ffb779c952426a69ae435ca1f57210c"},
+ {url = "https://files.pythonhosted.org/packages/4b/f9/2fa7d6cce0b9867ec3917878d03d5298d7a88522ed7dadc6cac4642bce8b/Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca31dd6014cb8b0b2db1e46081b0ca7d936f856da3b39744aef499db5d84d02"},
+ {url = "https://files.pythonhosted.org/packages/4e/8c/84131e85d4ebd600a823fad1707157d55055a7ea80ce6c8c2f6e2de93f2c/Pillow-9.3.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0a06a052c5f37b4ed81c613a455a81f9a3a69429b4fd7bb913c3fa98abefc20"},
+ {url = "https://files.pythonhosted.org/packages/56/02/2c8fde18b251c6ce2061b23b4fa04f3e71d386c725a09753bad19649e1b1/Pillow-9.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:51e0e543a33ed92db9f5ef69a0356e0b1a7a6b6a71b80df99f1d181ae5875636"},
+ {url = "https://files.pythonhosted.org/packages/5b/48/1e4437f90c0531c0a8f5c5b1e793e0f234fe8203019de04001213b4fb38e/Pillow-9.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:90fb88843d3902fe7c9586d439d1e8c05258f41da473952aa8b328d8b907498c"},
+ {url = "https://files.pythonhosted.org/packages/5f/1e/8a26e1ff2064b665f3923a727cb9240919cdcdce303e4800729635aefc40/Pillow-9.3.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0915e734b33a474d76c28e07292f196cdf2a590a0d25bcc06e64e545f2d146c"},
+ {url = "https://files.pythonhosted.org/packages/5f/71/ad106c6e3a28f7ff81b2e172a05c158181b61de2a6292896355f0e50f46c/Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c7025dce65566eb6e89f56c9509d4f628fddcedb131d9465cacd3d8bac337e7e"},
+ {url = "https://files.pythonhosted.org/packages/6c/a8/3cb2d902c4094b6ad06f069b937c2696fd47c5fc02da107e33470f468534/Pillow-9.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3dd6caf940756101205dffc5367babf288a30043d35f80936f9bfb37f8355b32"},
+ {url = "https://files.pythonhosted.org/packages/6d/d2/610687610050eb3ce0ebdd0a3f87d4bb3f1637a89f9fe00060a080a167da/Pillow-9.3.0-1-cp37-cp37m-win32.whl", hash = "sha256:e6ea6b856a74d560d9326c0f5895ef8050126acfdc7ca08ad703eb0081e82b74"},
+ {url = "https://files.pythonhosted.org/packages/6f/64/3acfbffd532ec2e82f69d4f22de9c29197540ae61926f5b7072654281e91/Pillow-9.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa4107d1b306cdf8953edde0534562607fe8811b6c4d9a486298ad31de733b2"},
+ {url = "https://files.pythonhosted.org/packages/77/56/bb8dc927b3c44df0ca938b4a17b08c59ef5e1e7cc06300bddf20d4e2935f/Pillow-9.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:276a5ca930c913f714e372b2591a22c4bd3b81a418c0f6635ba832daec1cbcfc"},
+ {url = "https://files.pythonhosted.org/packages/78/8c/ae0c7cfb6cae4eb0946b7369258f78e3104c22eeffda45ad6228bc0838f5/Pillow-9.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3033fbe1feb1b59394615a1cafaee85e49d01b51d54de0cbf6aa8e64182518a1"},
+ {url = "https://files.pythonhosted.org/packages/88/9f/012551c5a9d8b4d0304b66d16119b9c79bac052e0b0bd37de29b64c1bf0c/Pillow-9.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:073adb2ae23431d3b9bcbcff3fe698b62ed47211d0716b067385538a1b0f28b8"},
+ {url = "https://files.pythonhosted.org/packages/8a/e8/124718316b3f4f9fa24e4b7e915d8e02d58d43a5e3c44fcbceef17d60647/Pillow-9.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b90f7616ea170e92820775ed47e136208e04c967271c9ef615b6fbd08d9af0e3"},
+ {url = "https://files.pythonhosted.org/packages/a0/6c/6e11abff7f944b51dbeea1bdfe14ad21976858e822ef667fa4eb82ec6f7c/Pillow-9.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:41e0051336807468be450d52b8edd12ac60bebaa97fe10c8b660f116e50b30e4"},
+ {url = "https://files.pythonhosted.org/packages/a1/81/10f4f9665ce366c7501141c973c755cd5c9ebe577886ebfa7ca3e9a115f0/Pillow-9.3.0-cp38-cp38-win32.whl", hash = "sha256:f1ff2ee69f10f13a9596480335f406dd1f70c3650349e2be67ca3139280cade0"},
+ {url = "https://files.pythonhosted.org/packages/a7/9e/8acd4d170596fa876b0baaae3542de3b6d0a709b4652ce78285aff59e849/Pillow-9.3.0-cp37-cp37m-win32.whl", hash = "sha256:82409ffe29d70fd733ff3c1025a602abb3e67405d41b9403b00b01debc4c9a29"},
+ {url = "https://files.pythonhosted.org/packages/ac/0b/cb7ecd88cea2422818057db720966bbb56bbcec3b359f4a5cafdf737d9e5/Pillow-9.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9aaa107275d8527e9d6e7670b64aabaaa36e5b6bd71a1015ddd21da0d4e06448"},
+ {url = "https://files.pythonhosted.org/packages/af/7f/c5f7a35fd7053a4aee79c41fc4b125281745577c6ce3445e57ed8b4eff56/Pillow-9.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e0918e03aa0c72ea56edbb00d4d664294815aa11291a11504a377ea018330d3"},
+ {url = "https://files.pythonhosted.org/packages/b7/34/7a88f5ec5f26ac68d3d7a158c67c0a610a4e6b6d22c35266d0e715485b09/Pillow-9.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:6c738585d7a9961d8c2821a1eb3dcb978d14e238be3d70f0a706f7fa9316946b"},
+ {url = "https://files.pythonhosted.org/packages/b9/b8/3e2c23766019a9a03ee26ac6b7af9392aba8574806ce10e5074733987240/Pillow-9.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c7c8ae3864846fc95f4611c78129301e203aaa2af813b703c55d10cc1628535"},
+ {url = "https://files.pythonhosted.org/packages/bb/0e/83dd785dff8d62b4d663391ac62390ffadfcdfa4bd11959742f90d02127c/Pillow-9.3.0-cp39-cp39-win32.whl", hash = "sha256:bac18ab8d2d1e6b4ce25e3424f709aceef668347db8637c2296bcf41acb7cf48"},
+ {url = "https://files.pythonhosted.org/packages/bc/28/0bcf18f0c22cd89626ee2e071edbe5736d54f9031fe986ae94389776864b/Pillow-9.3.0-cp310-cp310-win32.whl", hash = "sha256:655a83b0058ba47c7c52e4e2df5ecf484c1b0b0349805896dd350cbc416bdd91"},
+ {url = "https://files.pythonhosted.org/packages/be/3e/a757fd2fdd5814aa0e9e1e838f79d33e0098e10a2e3afb7acdcb72278290/Pillow-9.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03150abd92771742d4a8cd6f2fa6246d847dcd2e332a18d0c15cc75bf6703040"},
+ {url = "https://files.pythonhosted.org/packages/c0/47/4023dab2d77ea3f687939770b06e0c191b4a5a20590f158a6e8dbb03e357/Pillow-9.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77ec3e7be99629898c9a6d24a09de089fa5356ee408cdffffe62d67bb75fdd72"},
+ {url = "https://files.pythonhosted.org/packages/c0/8f/dfa473f3a6241bff91ae8bb905bd0afceb827f37de2917a94b5c4b1112bf/Pillow-9.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:b472b5ea442148d1c3e2209f20f1e0bb0eb556538690fa70b5e1f79fa0ba8dc2"},
+ {url = "https://files.pythonhosted.org/packages/c1/c3/be8222fce0553e05264bfe66f2cc73483567b961f144acef88753fba9c6c/Pillow-9.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d77adcd56a42d00cc1be30843d3426aa4e660cab4a61021dc84467123f7a00c"},
+ {url = "https://files.pythonhosted.org/packages/c2/1d/5d0fd887bb790a42a9efb7fe742358f1eae332ee3120a7d4f79ee37358c9/Pillow-9.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:57751894f6618fd4308ed8e0c36c333e2f5469744c34729a27532b3db106ee20"},
+ {url = "https://files.pythonhosted.org/packages/c8/7c/f5fe5b37b6a8f2218db98f33d2278f2900e0fc4ef019cd4d9e522d6e3206/Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae5331c23ce118c53b172fa64a4c037eb83c9165aba3a7ba9ddd3ec9fa64a699"},
+ {url = "https://files.pythonhosted.org/packages/c9/b8/27c526c45f482450a53c0faab6c0c4baf9cddee0a8f879a8526f7dd8adf0/Pillow-9.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:828989c45c245518065a110434246c44a56a8b2b2f6347d1409c787e6e4651ee"},
+ {url = "https://files.pythonhosted.org/packages/ca/97/9677bf1d97c408779e5940b89675e38a8ebe8bcc9dbae8adfc9e7080a314/Pillow-9.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:73bd195e43f3fadecfc50c682f5055ec32ee2c933243cafbfdec69ab1aa87cad"},
+ {url = "https://files.pythonhosted.org/packages/cf/22/fed65d07e5e7b19cc608754e940a9b101bebe78cb6cd9d764639717848a4/Pillow-9.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ebf2029c1f464c59b8bdbe5143c79fa2045a581ac53679733d3a91d400ff9efb"},
+ {url = "https://files.pythonhosted.org/packages/d0/ae/b5e8750a7e745926e2227a43766d905c8c6a0f03739c7aec5aea9e975355/Pillow-9.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:871b72c3643e516db4ecf20efe735deb27fe30ca17800e661d769faab45a18d7"},
+ {url = "https://files.pythonhosted.org/packages/d1/6f/b5e87428a5566563b6d661824c694bbc4b0386aed1c939b9aec47d9ee573/Pillow-9.3.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22b012ea2d065fd163ca096f4e37e47cd8b59cf4b0fd47bfca6abb93df70b34c"},
+ {url = "https://files.pythonhosted.org/packages/d4/0d/e8f6ed8e2328007020bb242a0443d925f23ee06d059486c5f1308f2a5e26/Pillow-9.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:18498994b29e1cf86d505edcb7edbe814d133d2232d256db8c7a8ceb34d18cef"},
+ {url = "https://files.pythonhosted.org/packages/d5/31/b026f9f7c87adf8027f51f98f392f6558982485b7202af5f9276492b2141/Pillow-9.3.0-1-cp37-cp37m-win_amd64.whl", hash = "sha256:32a44128c4bdca7f31de5be641187367fe2a450ad83b833ef78910397db491aa"},
+ {url = "https://files.pythonhosted.org/packages/d5/79/191ea841f818aba4eb9783b9f91a3c16c51deff7326f5a2f47de54959a28/Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbb8e7f2abee51cef77673be97760abff1674ed32847ce04b4af90f610144c7b"},
+ {url = "https://files.pythonhosted.org/packages/d6/be/996d629efd03aa305472a17183fcdfe2dd8529acea767d0ba2242d90cbfa/Pillow-9.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:801ec82e4188e935c7f5e22e006d01611d6b41661bba9fe45b60e7ac1a8f84de"},
+ {url = "https://files.pythonhosted.org/packages/db/b0/6438c96e80d4cad5b6381ba774018de9d8eae591d65bdfd80e3ad630928a/Pillow-9.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:97aabc5c50312afa5e0a2b07c17d4ac5e865b250986f8afe2b02d772567a380c"},
+ {url = "https://files.pythonhosted.org/packages/dc/20/30e5ea4ecb35b36d9bc4ff4e8edc048e017a8e3d2e087a512b0622bfdde3/Pillow-9.3.0-cp311-cp311-win32.whl", hash = "sha256:3168434d303babf495d4ba58fc22d6604f6e2afb97adc6a423e917dab828939c"},
+ {url = "https://files.pythonhosted.org/packages/e0/59/74d88751801101259abd695f77c44b97a828b6a4e7981a79a75d2c3379ae/Pillow-9.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:502526a2cbfa431d9fc2a079bdd9061a2397b842bb6bc4239bb176da00993812"},
+ {url = "https://files.pythonhosted.org/packages/ec/4f/bc65f543b4d774d6c888cb0936a8b91cf7c5fbb25e16f923e332822b279b/Pillow-9.3.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4012d06c846dc2b80651b120e2cdd787b013deb39c09f407727ba90015c684f"},
+ {url = "https://files.pythonhosted.org/packages/f1/e4/2f7e4efeb79ab0a2c5ecba320b2e5a3aad7a49cc543407f4c1268ed804bb/Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:0b07fffc13f474264c336298d1b4ce01d9c5a011415b79d4ee5527bb69ae6f65"},
+ {url = "https://files.pythonhosted.org/packages/f7/11/763c3992c3e6d00e9f6de81b0d721306757bb7201155d135a16032460943/Pillow-9.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:ad58d27a5b0262c0c19b47d54c5802db9b34d38bbf886665b626aff83c74bacd"},
+ {url = "https://files.pythonhosted.org/packages/fc/12/32101461796addca02fd14dc4d2b76068a2cbf895002ed668550caf3fcc1/Pillow-9.3.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be55f8457cd1eac957af0c3f5ece7bc3f033f89b114ef30f710882717670b2a8"},
+]
+"platformdirs 2.5.4" = [
+ {url = "https://files.pythonhosted.org/packages/61/e0/15ba41c6716acb033c3793be3a02f26c53914ecd9bdd6b315001f8f5f581/platformdirs-2.5.4-py3-none-any.whl", hash = "sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10"},
+ {url = "https://files.pythonhosted.org/packages/cb/5f/dda8451435f17ed8043eab5ffe04e47d703debe8fe845eb074f42260e50a/platformdirs-2.5.4.tar.gz", hash = "sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7"},
+]
+"pluggy 1.0.0" = [
+ {url = "https://files.pythonhosted.org/packages/9e/01/f38e2ff29715251cf25532b9082a1589ab7e4f571ced434f98d0139336dc/pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
+ {url = "https://files.pythonhosted.org/packages/a1/16/db2d7de3474b6e37cbb9c008965ee63835bba517e22cdb8c35b5116b5ce1/pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
+]
+"protobuf 3.20.3" = [
+ {url = "https://files.pythonhosted.org/packages/00/e7/d23c439c55c90ae2e52184363162f7079ca3e7d86205b411d4e9dc266f81/protobuf-3.20.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:398a9e0c3eaceb34ec1aee71894ca3299605fa8e761544934378bbc6c97de23b"},
+ {url = "https://files.pythonhosted.org/packages/11/a5/e52b731415ad6ef3d841e9e6e337a690249e800cc7c06f0749afab26348c/protobuf-3.20.3-cp39-cp39-win_amd64.whl", hash = "sha256:03038ac1cfbc41aa21f6afcbcd357281d7521b4157926f30ebecc8d4ea59dcb7"},
+ {url = "https://files.pythonhosted.org/packages/28/55/b80e8567ec327c060fa39b242392e25690c8899c489ecd7bb65b46b7bb55/protobuf-3.20.3-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99"},
+ {url = "https://files.pythonhosted.org/packages/2a/7c/e7091f0eea6eec70547d28c6c0d8c7335ee58f6b13456608beec8c94a62a/protobuf-3.20.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:d9e4432ff660d67d775c66ac42a67cf2453c27cb4d738fc22cb53b5d84c135d4"},
+ {url = "https://files.pythonhosted.org/packages/31/be/80a9c6f16dfa4d41be3edbe655349778ae30882407fa8275eb46b4d34854/protobuf-3.20.3-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9aae4406ea63d825636cc11ffb34ad3379335803216ee3a856787bcf5ccc751e"},
+ {url = "https://files.pythonhosted.org/packages/32/f8/52f598bceb16fe365f4ef8e957ac8890aeb56abf97d365ff5abd8c1250cf/protobuf-3.20.3-cp38-cp38-win_amd64.whl", hash = "sha256:447d43819997825d4e71bf5769d869b968ce96848b6479397e29fc24c4a5dfe9"},
+ {url = "https://files.pythonhosted.org/packages/36/8b/433071fed0058322090a55021bdc8da76d16c7bc9823f5795797803dd6d0/protobuf-3.20.3-cp39-cp39-win32.whl", hash = "sha256:819559cafa1a373b7096a482b504ae8a857c89593cf3a25af743ac9ecbd23480"},
+ {url = "https://files.pythonhosted.org/packages/3c/14/16ef7da61d30a519d84e6841d096fe446c4d8a2c39b083b0376b4785f1f3/protobuf-3.20.3-cp38-cp38-win32.whl", hash = "sha256:c02ce36ec760252242a33967d51c289fd0e1c0e6e5cc9397e2279177716add86"},
+ {url = "https://files.pythonhosted.org/packages/4c/12/62e1d5505c172e1a7f803d83b0b1693f7952c3c271eb2f155703012ae67a/protobuf-3.20.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:74480f79a023f90dc6e18febbf7b8bac7508420f2006fabd512013c0c238f454"},
+ {url = "https://files.pythonhosted.org/packages/55/5b/e3d951e34f8356e5feecacd12a8e3b258a1da6d9a03ad1770f28925f29bc/protobuf-3.20.3.tar.gz", hash = "sha256:2e3427429c9cffebf259491be0af70189607f365c2f41c7c3764af6f337105f2"},
+ {url = "https://files.pythonhosted.org/packages/6f/5e/fc6feb366b0a9f28e0a2de3b062667c521cd9517d4ff55077b8f351ba2f3/protobuf-3.20.3-cp310-cp310-win_amd64.whl", hash = "sha256:67a3598f0a2dcbc58d02dd1928544e7d88f764b47d4a286202913f0b2801c2e7"},
+ {url = "https://files.pythonhosted.org/packages/8d/14/619e24a4c70df2901e1f4dbc50a6291eb63a759172558df326347dce1f0d/protobuf-3.20.3-py2.py3-none-any.whl", hash = "sha256:a7ca6d488aa8ff7f329d4c545b2dbad8ac31464f1d8b1c87ad1346717731e4db"},
+ {url = "https://files.pythonhosted.org/packages/98/07/4c75a689fa173c12b92c9a64a82efad44797b9b2b784c8562f36ab28b551/protobuf-3.20.3-cp37-cp37m-win_amd64.whl", hash = "sha256:8c0c984a1b8fef4086329ff8dd19ac77576b384079247c770f29cc8ce3afa06c"},
+ {url = "https://files.pythonhosted.org/packages/99/25/5825472ecd911f4ac2ac4e9ab039a48b6d03874e2add92fb633e080bf3eb/protobuf-3.20.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bf01b5720be110540be4286e791db73f84a2b721072a3711efff6c324cdf074b"},
+ {url = "https://files.pythonhosted.org/packages/9c/d8/dc6a9ee6ec43a1001e3d71cccda70cf50ac0098000fc455023dba3b46ebf/protobuf-3.20.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:899dc660cd599d7352d6f10d83c95df430a38b410c1b66b407a6b29265d66469"},
+ {url = "https://files.pythonhosted.org/packages/9f/1a/6848ed1669a6c70bf947d25d64ce6dcc65ccec06e917072df516944fa17e/protobuf-3.20.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:de78575669dddf6099a8a0f46a27e82a1783c557ccc38ee620ed8cc96d3be7d7"},
+ {url = "https://files.pythonhosted.org/packages/b5/b6/ec87636b9381137f17292851461902ceac7632a00476c2afbcd864ed5447/protobuf-3.20.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:f4c42102bc82a51108e449cbb32b19b180022941c727bac0cfd50170341f16ee"},
+ {url = "https://files.pythonhosted.org/packages/c7/df/ec3ecb8c940b36121c7b77c10acebf3d1c736498aa2f1fe3b6231ee44e76/protobuf-3.20.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:daa564862dd0d39c00f8086f88700fdbe8bc717e993a21e90711acfed02f2402"},
+ {url = "https://files.pythonhosted.org/packages/da/e4/4d62585593e9f962cb02614534f62f930de6a80a0a3784282094a01919b2/protobuf-3.20.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:44246bab5dd4b7fbd3c0c80b6f16686808fab0e4aca819ade6e8d294a29c7050"},
+ {url = "https://files.pythonhosted.org/packages/db/96/948d3fcc1fa816e7ae1d27af59b9d8c5c5e582f3994fd14394f31da95b99/protobuf-3.20.3-cp310-cp310-win32.whl", hash = "sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c"},
+ {url = "https://files.pythonhosted.org/packages/ef/d4/765a106ca96d487f94f3c99e46b399218f53735628c3b2d759a832e2adab/protobuf-3.20.3-cp37-cp37m-win32.whl", hash = "sha256:b6cc7ba72a8850621bfec987cb72623e703b7fe2b9127a161ce61e61558ad905"},
+ {url = "https://files.pythonhosted.org/packages/fe/8f/d9db035740002d61b4140aaef53a8bac7e316b18ec8744eb6c1fcf83c310/protobuf-3.20.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e64857f395505ebf3d2569935506ae0dfc4a15cb80dc25261176c784662cdcc4"},
+ {url = "https://test-files.pythonhosted.org/packages/8d/14/619e24a4c70df2901e1f4dbc50a6291eb63a759172558df326347dce1f0d/protobuf-3.20.3-py2.py3-none-any.whl", hash = "sha256:a7ca6d488aa8ff7f329d4c545b2dbad8ac31464f1d8b1c87ad1346717731e4db"},
+ {url = "https://test-files.pythonhosted.org/packages/f4/de/904b3f09af0e6a2f0072a25878d784d151283546a5bb3e2ba12def8997c6/protobuf-3.20.3.tar.gz", hash = "sha256:e8a258d4d36b076db944e6ed7f023b47443e197d134858cb0e87b7f9b973e9bd"},
+]
+"py-sr25519-bindings 0.1.4" = [
+ {url = "https://files.pythonhosted.org/packages/00/17/33406429b979af786a6cf2066b6463e224a2ac763105e82483f513bfaadb/py_sr25519_bindings-0.1.4-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:721cae28e86038682dd522191f9cb58a71b3455ef461433ba27a76d2d321197c"},
+ {url = "https://files.pythonhosted.org/packages/06/41/3d76392a2e0dd37e3035528291c84488e9d074f16e121565dde1928f1bd4/py_sr25519_bindings-0.1.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:8e62369aca9455131330cf63b0fcec3be198cabad936317321aa0db9d143435b"},
+ {url = "https://files.pythonhosted.org/packages/08/18/c25beaad05e559c7105e40b97aa2929cc8c0174b9b69bd8c4a7cf0705ec6/py_sr25519_bindings-0.1.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:aed508e15e695c5e3147be936db7a40f7bf5cb0a4078a9eb0e25fbdeb7c08982"},
+ {url = "https://files.pythonhosted.org/packages/0c/81/cbe028f361e6d395635ab761e7324adaa3db5dd9c8e8a44df74202d698e3/py_sr25519_bindings-0.1.4-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb1edb9db13548252fe3410868111dcda48a5b6ecda11cebb156930f0f472368"},
+ {url = "https://files.pythonhosted.org/packages/21/36/8a3eb5cf00999d6f137bfe2a1e2a6e374f35e06d6cf31aa082142c1a1ef1/py_sr25519_bindings-0.1.4-cp37-cp37m-manylinux_2_28_armv7l.whl", hash = "sha256:6be4f52320d021fe8a86d72d71fae821b6c251f12b36e7e3deee5a1140da55b6"},
+ {url = "https://files.pythonhosted.org/packages/25/c0/3041d947eaeabf8f84032934bc7a1f39a308befd59f27896f891f2fe9aa7/py_sr25519_bindings-0.1.4-cp38-none-win_amd64.whl", hash = "sha256:127057216b8cd32ea322d64aa52e70ba7b42a55cd4eb31e8df7c0120934f2445"},
+ {url = "https://files.pythonhosted.org/packages/29/c6/e18d4c1a2882d1f1389948c859d8bfe0a0cebe4e7cb118b9516c09488c8e/py_sr25519_bindings-0.1.4-cp37-none-win_amd64.whl", hash = "sha256:d13e3a6b262494f66f36de3ba45d1040fee7a0c4104d56086b15315332026db9"},
+ {url = "https://files.pythonhosted.org/packages/38/21/dd5ca55d51bf77ad63f641e080c4cc6a0b09bccd07bf44ec9886e2e29107/py_sr25519_bindings-0.1.4-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:5a0f591f9b474ca31f0ca199ebc5d2c70a8869ff6e211188a1562310614558dd"},
+ {url = "https://files.pythonhosted.org/packages/3f/02/0d807a6e7d970580485c65b55e8f3a6d737da6700de4ef23dde8cc6fd1be/py_sr25519_bindings-0.1.4-cp310-none-win_amd64.whl", hash = "sha256:1ca0d8e1ca66a17c41e3f6da0c3837e7dbf5b391eb4ea41cfe6026bad0adc8ea"},
+ {url = "https://files.pythonhosted.org/packages/44/02/d8a4d2e5b314e8b4d9d0e18f3ce8b89b3fe5e59fbb0413e787c8b1f6e671/py_sr25519_bindings-0.1.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6715852eb2ea733c4978b77f9a049cc9eb763d54270d5b1263b198256656c38e"},
+ {url = "https://files.pythonhosted.org/packages/4c/2c/d263ede4827a7a5e02290b7e8d34515b244897d80005ea345d69311cb99d/py_sr25519_bindings-0.1.4-cp39-none-win32.whl", hash = "sha256:0a09f5886a706fcee6786f94dd59a04bdc95f2d6c4b7e1b3118ca805d322a055"},
+ {url = "https://files.pythonhosted.org/packages/64/82/f55600547af686b1ef3db123471493800ce6f9a61ac7a9e355b3bd6360aa/py_sr25519_bindings-0.1.4.tar.gz", hash = "sha256:5e12ca977014f148f4bfb6ba662f1529b15cc8ce030719d726c4e16a379e976e"},
+ {url = "https://files.pythonhosted.org/packages/6a/7c/b4c3ea21d696507b7bf129e6ee1eda0a7bb505604ff5aebe8666a021f7d9/py_sr25519_bindings-0.1.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b901675f8ea8ca4a6e55887ea48448a3f6bf55f22baa4acfa77d7c844e82e9db"},
+ {url = "https://files.pythonhosted.org/packages/74/a7/377ba7820127f72a97cf6355cfa59ee92a767e64c0730d3c2de5b99f3a36/py_sr25519_bindings-0.1.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3f52666b02075483dfdd294b9d0fd903eaf96ac7140e5be45390b25ded52ca6a"},
+ {url = "https://files.pythonhosted.org/packages/76/cb/0e5541edee87356945beac027e9180cdb1a7850c92890c76e1b45d67d24f/py_sr25519_bindings-0.1.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4940ebfcb482b33468c41dda9e8c19137c14cc3b993e1084e55387c6acfa11ab"},
+ {url = "https://files.pythonhosted.org/packages/7b/48/7ff1093cf5237c79c7d5c80693b19f0bb2c9fc46f828b596ca65add25cc0/py_sr25519_bindings-0.1.4-cp38-cp38-manylinux_2_28_armv7l.whl", hash = "sha256:9b80e4aa4037b1e8fd3d5ff0fb4e2c3d92e04b2936f29c79d5411c5a22562f25"},
+ {url = "https://files.pythonhosted.org/packages/7e/7c/b6f9996af8ccd71ee9436e69a0e35229805e44fa12913a276230d3e43493/py_sr25519_bindings-0.1.4-cp38-none-win32.whl", hash = "sha256:ce3639509e87ab04652c62aab626f13a80e308347047173bf74749397d0be539"},
+ {url = "https://files.pythonhosted.org/packages/81/07/fa3613aa467368f6a8bbbba1342e2e4ad5f01c4c69bdbae320910c748d7d/py_sr25519_bindings-0.1.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:67dd46da0a571859418fc938af15ab9a0586e47244d21f0f26c5cbf4daf10a3b"},
+ {url = "https://files.pythonhosted.org/packages/82/32/8ceb7a90a7ef390c06483ddf801da300f0f53b9d534d54d20b191b6ae70a/py_sr25519_bindings-0.1.4-cp310-cp310-manylinux_2_28_armv7l.whl", hash = "sha256:34635da67e6798bb4535543e63c3e1d3b19f17a4df1f9b613c69b66fc8296fa3"},
+ {url = "https://files.pythonhosted.org/packages/93/65/c2571cd841cacbc686b992d3dc2c86943300e5b018277865b02e627623f3/py_sr25519_bindings-0.1.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1121e5da273c81b2e7ba0ca4bef8a89c527a6e25ed3110a95e93fd2caa04c50b"},
+ {url = "https://files.pythonhosted.org/packages/9a/b2/de252739677fbbae043cdec66c85c41947fe45e05a262f36afb417f3b287/py_sr25519_bindings-0.1.4-cp39-cp39-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:197892cd89077baf2be51e3307a40de2c32006a2b89fec7d767a1d0b2749712d"},
+ {url = "https://files.pythonhosted.org/packages/a3/6a/da96376c02c6b11bb1ec84dd225016bcaebd39e73ed0635b39017e0c919a/py_sr25519_bindings-0.1.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d1c1247b4bf4c5670767e8a35bc259b425b275c8d72c6e3f1cfac47cf9b80e89"},
+ {url = "https://files.pythonhosted.org/packages/a5/ae/16878ea5c1be67708bfd39b7e3f98fa29f0a22a8952499daa18568abc528/py_sr25519_bindings-0.1.4-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:d1b1b51d997363a264fb3f91978f21ea784f5ee50ed26e11ed0a0f26c497a4eb"},
+ {url = "https://files.pythonhosted.org/packages/ae/58/3af038d8a625f4fe6b2a8208a3aabdd7d7fa7a56adc8506f9a2ec7d02e2c/py_sr25519_bindings-0.1.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3845f55a88dcba825c16e65fc9e26742cbb9103fba5229fd0c3e9e8deffd323b"},
+ {url = "https://files.pythonhosted.org/packages/af/5f/a876044eba658ed2272cc640d56cc13c1378f801b0d921858fbaaa677a64/py_sr25519_bindings-0.1.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72b49cba419f5c76436bf06de4575ee27713afd8b867c4c14cb7a29e1046c30a"},
+ {url = "https://files.pythonhosted.org/packages/b0/94/439f7574765167520ef20f89a2d11e77fafa097ba7e0b212942f3d29384a/py_sr25519_bindings-0.1.4-cp39-none-win_amd64.whl", hash = "sha256:855af2efee23ebbb4b5544027b2b9a4818db34717b88741bcfd74f5739e9152f"},
+ {url = "https://files.pythonhosted.org/packages/b3/ef/342e55303451366e28381fd48117b248168fdb251995ded395364389b185/py_sr25519_bindings-0.1.4-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:14a06ca42d5fd07673138bf9970db9091059a1e4d704545bdf156dd05de3e3c6"},
+ {url = "https://files.pythonhosted.org/packages/b8/98/b40bc2afa4e1a47fa18b083cf467a437512377366746c1100e53b517a554/py_sr25519_bindings-0.1.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:42097fab2702186a6591471bc366d7c804c7c30744acd59d6c6a38fdcad4bedb"},
+ {url = "https://files.pythonhosted.org/packages/c5/a2/0d05e8b565cddfb32fa21ea78fcea9e400360217e1955aa7047eafe2a977/py_sr25519_bindings-0.1.4-cp39-cp39-manylinux_2_28_armv7l.whl", hash = "sha256:465c2ae7e3191f24ac10519aece185772a6b9d6179b8948fad78c3b1dbe77d8c"},
+ {url = "https://files.pythonhosted.org/packages/ca/80/d476d745110b1a727e976242a1f7fbaee47115b7c093fcc0f4dd99576887/py_sr25519_bindings-0.1.4-cp37-none-win32.whl", hash = "sha256:1fb9ca7b65e60b64bd6e81ee2d5e6556d93b317af3c95a2e24bf45a3c44041a0"},
+ {url = "https://files.pythonhosted.org/packages/cd/dd/904edf234ede3847ff879fe2ca65cf3085255442935e006ea02306afd1bb/py_sr25519_bindings-0.1.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:34786ba33f602d9d4f2495bd29d9f0cb357813a6ee8ae5cd9d37dbda86c141a4"},
+ {url = "https://files.pythonhosted.org/packages/e3/d8/7fb1e71f673c4d50fc54051ed7a3c08612b8e9b86d6d086ce31c3220cdf3/py_sr25519_bindings-0.1.4-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:9cbee7341ed35d08533c7dac4d227e2fe21b815072ef66178a7ddb77f4e7be36"},
+ {url = "https://files.pythonhosted.org/packages/e8/6d/d10583af0eb1f8d01f4ec641ee72d45893327e037333586d3b9e2ede4857/py_sr25519_bindings-0.1.4-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:9e07cc588754da27c03185e2d3823537790f616b5aab3df1703ca0f6f578114c"},
+ {url = "https://files.pythonhosted.org/packages/e8/99/a9d5da4aea85990fe4d088bebfff28d27067dc8d5249763b748821691953/py_sr25519_bindings-0.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e5118cc7e1a77083307d260ccda21a0ec14efb0b98535f8fc9e2c54c7594836"},
+ {url = "https://files.pythonhosted.org/packages/f6/39/08b76be2855ebf643e3a5de5de7fc383b961221e3911df547806be9561c8/py_sr25519_bindings-0.1.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ec17c935f8ae9a00bb5d6adac6cc455fd6052042959ace5563448c355e2960a"},
+ {url = "https://files.pythonhosted.org/packages/f8/dd/38bd462934574c44f8e7ca635d5fcc843fbaa3e1252724183c67651dd695/py_sr25519_bindings-0.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0046dda17c554376f5ba11ea91163b1b883ac61fcc1b1ba588e31b1cb58add28"},
+ {url = "https://files.pythonhosted.org/packages/ff/35/b25799b4ce26db05c08027dfbafa6e7f60812576d22b02e5e52db4531c90/py_sr25519_bindings-0.1.4-cp310-none-win32.whl", hash = "sha256:f7ca8f4e62aee8bc33916f6422e8ac5ffeeb96bc11f7ca52792354b771216bd8"},
+ {url = "https://test-files.pythonhosted.org/packages/1d/f9/7680e56c118871726cf7d75cf5b6fce8eb2444748d85db675b82925a410a/py_sr25519_bindings-0.1.4-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:938190d97f27566953468caf19baba3c7ee776d60eef816c7d233224624b00a6"},
+ {url = "https://test-files.pythonhosted.org/packages/5e/3d/752274043b7af794cfb7e541127d35df56bfb0b42120813c54ab52c9628b/py_sr25519_bindings-0.1.4.tar.gz", hash = "sha256:d9c7fd291c3233de404161c739b0626ff10ace60aabfd8334e4a8432a0a9e702"},
+]
+"pycparser 2.21" = [
+ {url = "https://files.pythonhosted.org/packages/5e/0b/95d387f5f4433cb0f53ff7ad859bd2c6051051cebbb564f139a999ab46de/pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
+ {url = "https://files.pythonhosted.org/packages/62/d5/5f610ebe421e85889f2e55e33b7f9a6795bd982198517d912eb1c76e1a53/pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
+]
+"pycryptodome 3.15.0" = [
+ {url = "https://files.pythonhosted.org/packages/00/07/5a262e3213a9358e2f7caf9080aa8a984f44bf4aee84592dfb965dd34355/pycryptodome-3.15.0-cp35-abi3-win_amd64.whl", hash = "sha256:c77126899c4b9c9827ddf50565e93955cb3996813c18900c16b2ea0474e130e9"},
+ {url = "https://files.pythonhosted.org/packages/01/bc/7c67348624581fc57e5cb34e650ba09ba668e08e41937d1d1bbdc8cd9e9b/pycryptodome-3.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:7c9ed8aa31c146bef65d89a1b655f5f4eab5e1120f55fc297713c89c9e56ff0b"},
+ {url = "https://files.pythonhosted.org/packages/11/e4/a8e8056a59c39f8c9ddd11d3bc3e1a67493abe746df727e531f66ecede9e/pycryptodome-3.15.0.tar.gz", hash = "sha256:9135dddad504592bcc18b0d2d95ce86c3a5ea87ec6447ef25cfedea12d6018b8"},
+ {url = "https://files.pythonhosted.org/packages/1e/ed/e908d15473f14975f1b29d52de57fee7b035f87ff9560f9dae2e37bf9bc2/pycryptodome-3.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:2ea63d46157386c5053cfebcdd9bd8e0c8b7b0ac4a0507a027f5174929403884"},
+ {url = "https://files.pythonhosted.org/packages/2e/6f/27fbd8f3fd8b48feba2b4226f7f8d23af7755c54957fccc3fe6f44b764cf/pycryptodome-3.15.0-cp35-abi3-manylinux1_i686.whl", hash = "sha256:4c3ccad74eeb7b001f3538643c4225eac398c77d617ebb3e57571a897943c667"},
+ {url = "https://files.pythonhosted.org/packages/2f/dc/e5bb825eb7348773b77ace0d977f549af851c1d8300f1ba60119e88ba715/pycryptodome-3.15.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9ee40e2168f1348ae476676a2e938ca80a2f57b14a249d8fe0d3cdf803e5a676"},
+ {url = "https://files.pythonhosted.org/packages/34/09/ab89d75316862ae9fced5516ba533dc17da89938e7de4d5ddfd8483fd9e8/pycryptodome-3.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:0926f7cc3735033061ef3cf27ed16faad6544b14666410727b31fea85a5b16eb"},
+ {url = "https://files.pythonhosted.org/packages/35/5b/ba592bfd0d3bad9450645b751c132cf1028dc111ae699fd8e70808414941/pycryptodome-3.15.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:092a26e78b73f2530b8bd6b3898e7453ab2f36e42fd85097d705d6aba2ec3e5e"},
+ {url = "https://files.pythonhosted.org/packages/38/a7/ff3d1e9ef28726433b5d6edb5ded96a0b9d85722dad9c3faf27a1372b0a3/pycryptodome-3.15.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:9c772c485b27967514d0df1458b56875f4b6d025566bf27399d0c239ff1b369f"},
+ {url = "https://files.pythonhosted.org/packages/55/60/28d873c1efe46cf62494a0393fe34e4757821123fb1af9c45be3b2eeba8a/pycryptodome-3.15.0-cp35-abi3-win32.whl", hash = "sha256:e244ab85c422260de91cda6379e8e986405b4f13dc97d2876497178707f87fc1"},
+ {url = "https://files.pythonhosted.org/packages/5a/3d/56084bd2b973c262a59d6b7c5618d93f8ee6045c484e7675e97104e48ac3/pycryptodome-3.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:c3640deff4197fa064295aaac10ab49a0d55ef3d6a54ae1499c40d646655c89f"},
+ {url = "https://files.pythonhosted.org/packages/5c/9c/2cfbb08a3f573e35818fe49d4f6efdc6c157553b71bc7d65592de49f623f/pycryptodome-3.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:7e3a8f6ee405b3bd1c4da371b93c31f7027944b2bcce0697022801db93120d83"},
+ {url = "https://files.pythonhosted.org/packages/6a/09/84ac32b49e991308749d615e7a5b9ae13b94adb01279224fbca584636977/pycryptodome-3.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2ffd8b31561455453ca9f62cb4c24e6b8d119d6d531087af5f14b64bee2c23e6"},
+ {url = "https://files.pythonhosted.org/packages/6c/73/7b25e21cbb4aa8817f85fa86537798d681f3dd479fd5d4737e9f3efbaf9e/pycryptodome-3.15.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:2ec709b0a58b539a4f9d33fb8508264c3678d7edb33a68b8906ba914f71e8c13"},
+ {url = "https://files.pythonhosted.org/packages/74/4d/1340e63264e07ff5f1e1daec9d66015ade99d64f3b966a52ff7ff3f4a362/pycryptodome-3.15.0-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:ecaaef2d21b365d9c5ca8427ffc10cebed9d9102749fd502218c23cb9a05feb5"},
+ {url = "https://files.pythonhosted.org/packages/74/f8/e6e9e2426f332b2216950df88bdf160ed90b2dbe42dfd5fc5e8ac33bd583/pycryptodome-3.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:2aa55aae81f935a08d5a3c2042eb81741a43e044bd8a81ea7239448ad751f763"},
+ {url = "https://files.pythonhosted.org/packages/7d/ac/843a78bc3c5c680f4c22f530bdb6e2927b770f77630948b0e0247cc23c04/pycryptodome-3.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ff7ae90e36c1715a54446e7872b76102baa5c63aa980917f4aa45e8c78d1a3ec"},
+ {url = "https://files.pythonhosted.org/packages/7d/be/e3e56f7f92bebf506aec486eb71d91952d2e9faf5e10872a89931db4120f/pycryptodome-3.15.0-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:4b52cb18b0ad46087caeb37a15e08040f3b4c2d444d58371b6f5d786d95534c2"},
+ {url = "https://files.pythonhosted.org/packages/86/93/93d5752292d6cf2709d9c3343c26e5a6f308976a566b6e4a389094b83fbe/pycryptodome-3.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:5099c9ca345b2f252f0c28e96904643153bae9258647585e5e6f649bb7a1844a"},
+ {url = "https://files.pythonhosted.org/packages/9b/c0/6aac989804de5526099062b263be17c7216901fc2fbbc4e08b6e44d9c236/pycryptodome-3.15.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:045d75527241d17e6ef13636d845a12e54660aa82e823b3b3341bcf5af03fa79"},
+ {url = "https://files.pythonhosted.org/packages/9b/e8/628f92b38ee4475d7b316d04c2913d397cdcc3f3a873bdbea10a438ba9fe/pycryptodome-3.15.0-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:ff287bcba9fbeb4f1cccc1f2e90a08d691480735a611ee83c80a7d74ad72b9d9"},
+ {url = "https://files.pythonhosted.org/packages/b1/54/ad3e2e07a5a7ceb926971398f7e3a0eeee85b6e1d3e658df8f71a4497daa/pycryptodome-3.15.0-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:1b22bcd9ec55e9c74927f6b1f69843cb256fb5a465088ce62837f793d9ffea88"},
+ {url = "https://files.pythonhosted.org/packages/b1/6c/4ee93e2e863e95caffc5a356b867e86f5596f4e38cddb43848412dd69176/pycryptodome-3.15.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:60b4faae330c3624cc5a546ba9cfd7b8273995a15de94ee4538130d74953ec2e"},
+ {url = "https://files.pythonhosted.org/packages/b1/d5/4a140b9d316681e9d2e55ac8a29f7f70b446c795e0af5f3de2500d7654b0/pycryptodome-3.15.0-cp27-cp27m-win32.whl", hash = "sha256:fd2184aae6ee2a944aaa49113e6f5787cdc5e4db1eb8edb1aea914bd75f33a0c"},
+ {url = "https://files.pythonhosted.org/packages/b1/e4/079a70b03928a01d5517cc69a5cf4bca79df7c3d85e27e4e66e9b4e211e7/pycryptodome-3.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:b9c5b1a1977491533dfd31e01550ee36ae0249d78aae7f632590db833a5012b8"},
+ {url = "https://files.pythonhosted.org/packages/b5/de/a1d1407e0bfd396e62c9efe3261be0c76888a8f3722b9b7f61f460e0e328/pycryptodome-3.15.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:d2a39a66057ab191e5c27211a7daf8f0737f23acbf6b3562b25a62df65ffcb7b"},
+ {url = "https://files.pythonhosted.org/packages/bb/7a/0e51d1dc253d0571106009b94762b8ab5a7c905079c354247b721ae1f198/pycryptodome-3.15.0-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:9eaadc058106344a566dc51d3d3a758ab07f8edde013712bc8d22032a86b264f"},
+ {url = "https://files.pythonhosted.org/packages/c5/b4/526dd68f6c8ff6b785d7a08da7a6bba5646cced508454f1d1ab948e6b737/pycryptodome-3.15.0-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:57f565acd2f0cf6fb3e1ba553d0cb1f33405ec1f9c5ded9b9a0a5320f2c0bd3d"},
+ {url = "https://files.pythonhosted.org/packages/c7/d0/319a673a6514beb9a203fdb60f28b87135bbbdda0b3ea782c022414a9034/pycryptodome-3.15.0-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b9cc96e274b253e47ad33ae1fccc36ea386f5251a823ccb50593a935db47fdd2"},
+ {url = "https://files.pythonhosted.org/packages/c8/03/1f745c158299000664a6fb5c0b28d5359837b05b4ba1f5a37645da5cad4d/pycryptodome-3.15.0-pp27-pypy_73-win32.whl", hash = "sha256:a8f06611e691c2ce45ca09bbf983e2ff2f8f4f87313609d80c125aff9fad6e7f"},
+]
+"pyelftools 0.29" = [
+ {url = "https://files.pythonhosted.org/packages/04/7c/867630e6e6293793f838b31034aa1875e1c3bd8c1ec34a0929a2506f350c/pyelftools-0.29-py2.py3-none-any.whl", hash = "sha256:519f38cf412f073b2d7393aa4682b0190fa901f7c3fa0bff2b82d537690c7fc1"},
+ {url = "https://files.pythonhosted.org/packages/0e/35/e76da824595452a5ad07f289ea1737ca0971fc6cc7b6ee9464279be06b5e/pyelftools-0.29.tar.gz", hash = "sha256:ec761596aafa16e282a31de188737e5485552469ac63b60cfcccf22263fd24ff"},
+]
+"pynacl 1.5.0" = [
+ {url = "https://files.pythonhosted.org/packages/25/2d/b7df6ddb0c2a33afdb358f8af6ea3b8c4d1196ca45497dd37a56f0c122be/PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"},
+ {url = "https://files.pythonhosted.org/packages/3d/85/c262db650e86812585e2bc59e497a8f59948a005325a11bbbc9ecd3fe26b/PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"},
+ {url = "https://files.pythonhosted.org/packages/59/bb/fddf10acd09637327a97ef89d2a9d621328850a72f1fdc8c08bdf72e385f/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"},
+ {url = "https://files.pythonhosted.org/packages/5d/70/87a065c37cca41a75f2ce113a5a2c2aa7533be648b184ade58971b5f7ccc/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"},
+ {url = "https://files.pythonhosted.org/packages/5e/22/d3db169895faaf3e2eda892f005f433a62db2decbcfbc2f61e6517adfa87/PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"},
+ {url = "https://files.pythonhosted.org/packages/66/28/ca86676b69bf9f90e710571b67450508484388bfce09acf8a46f0b8c785f/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"},
+ {url = "https://files.pythonhosted.org/packages/a7/22/27582568be639dfe22ddb3902225f91f2f17ceff88ce80e4db396c8986da/PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"},
+ {url = "https://files.pythonhosted.org/packages/ce/75/0b8ede18506041c0bf23ac4d8e2971b4161cd6ce630b177d0a08eb0d8857/PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"},
+ {url = "https://files.pythonhosted.org/packages/ee/87/f1bb6a595f14a327e8285b9eb54d41fef76c585a0edef0a45f6fc95de125/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"},
+ {url = "https://files.pythonhosted.org/packages/fd/1a/cc308a884bd299b651f1633acb978e8596c71c33ca85e9dc9fa33a5399b9/PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"},
+]
+"pyparsing 3.0.9" = [
+ {url = "https://files.pythonhosted.org/packages/6c/10/a7d0fa5baea8fe7b50f448ab742f26f52b80bfca85ac2be9d35cdd9a3246/pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
+ {url = "https://files.pythonhosted.org/packages/71/22/207523d16464c40a0310d2d4d8926daffa00ac1f5b1576170a32db749636/pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
+]
+"pyqt5 5.15.7" = [
+ {url = "https://files.pythonhosted.org/packages/14/75/596d5e9ed7a135918bb157ed315004ac008e09b9b4c9328f94568c88f003/PyQt5-5.15.7-cp37-abi3-macosx_10_13_x86_64.whl", hash = "sha256:1a793748c60d5aff3850b7abf84d47c1d41edb11231b7d7c16bef602c36be643"},
+ {url = "https://files.pythonhosted.org/packages/22/0d/4ea05714826a5f6478dc2845cd94ad76dce3c4c0ead0842b06cacd4e3492/PyQt5-5.15.7-cp37-abi3-manylinux1_x86_64.whl", hash = "sha256:e319c9d8639e0729235c1b09c99afdadad96fa3dbd8392ab561b5ab5946ee6ef"},
+ {url = "https://files.pythonhosted.org/packages/bd/85/31a12415765acb48fddac3e207cfffcbbae826fe194cf1d92179d8872f59/PyQt5-5.15.7-cp37-abi3-win_amd64.whl", hash = "sha256:232fe5b135a095cbd024cf341d928fc672c963f88e6a52b0c605be8177c2fdb5"},
+ {url = "https://files.pythonhosted.org/packages/c6/ea/5fa344ed69086218b30542c3b2e381c1f52ec4b141f878e9601e3b81dc71/PyQt5-5.15.7-cp37-abi3-win32.whl", hash = "sha256:08694f0a4c7d4f3d36b2311b1920e6283240ad3b7c09b515e08262e195dcdf37"},
+ {url = "https://files.pythonhosted.org/packages/e1/57/2023316578646e1adab903caab714708422f83a57f97eb34a5d13510f4e1/PyQt5-5.15.7.tar.gz", hash = "sha256:755121a52b3a08cb07275c10ebb96576d36e320e572591db16cfdbc558101594"},
+]
+"pyqt5-qt5 5.15.2" = [
+ {url = "https://files.pythonhosted.org/packages/1c/7e/ce7c66a541a105fa98b41d6405fe84940564695e29fc7dccf6d9e8c5f898/PyQt5_Qt5-5.15.2-py3-none-win32.whl", hash = "sha256:9cc7a768b1921f4b982ebc00a318ccb38578e44e45316c7a4a850e953e1dd327"},
+ {url = "https://files.pythonhosted.org/packages/37/97/5d3b222b924fa2ed4c2488925155cd0b03fd5d09ee1cfcf7c553c11c9f66/PyQt5_Qt5-5.15.2-py3-none-win_amd64.whl", hash = "sha256:750b78e4dba6bdf1607febedc08738e318ea09e9b10aea9ff0d73073f11f6962"},
+ {url = "https://files.pythonhosted.org/packages/62/09/99a222b0360616250fb2e6003a54e43a2a06b0774f0f8d5daafb86a2c375/PyQt5_Qt5-5.15.2-py3-none-macosx_10_13_intel.whl", hash = "sha256:76980cd3d7ae87e3c7a33bfebfaee84448fd650bad6840471d6cae199b56e154"},
+ {url = "https://files.pythonhosted.org/packages/83/d4/241a6a518d0bcf0a9fcdcbad5edfed18d43e884317eab8d5230a2b27e206/PyQt5_Qt5-5.15.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:1988f364ec8caf87a6ee5d5a3a5210d57539988bf8e84714c7d60972692e2f4a"},
+]
+"pyqt5-sip 12.11.0" = [
+ {url = "https://files.pythonhosted.org/packages/0a/0a/5386772f8e4fe865d306e9e6d91f3605c09209a3e226919b898270127340/PyQt5_sip-12.11.0-cp37-cp37m-win_amd64.whl", hash = "sha256:b69a1911f768b489846335e31e49eb34795c6b5a038ca24d894d751e3b0b44da"},
+ {url = "https://files.pythonhosted.org/packages/0d/1c/ccc6fd48150c07cbae52f6d2c9c40500e3252aa23cd98d5813973b7a150a/PyQt5_sip-12.11.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:205f3e1b3eea3597d8e878936c1a06e04bd23a59e8b179ee806465d72eea3071"},
+ {url = "https://files.pythonhosted.org/packages/0e/bf/af987c45abd8604efcc99551550546f8cdef5e986b258eb09e0e88ede239/PyQt5_sip-12.11.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9356260d4feb60dbac0ab66f8a791a0d2cda1bf98c9dec8e575904a045fbf7c5"},
+ {url = "https://files.pythonhosted.org/packages/12/0f/54228b287741959a486d0a38a6626e0ba0ffc7c1c8d4fab0951c77c11e3a/PyQt5_sip-12.11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bd733667098cac70e89279d9c239106d543fb480def62a44e6366ccb8f68510b"},
+ {url = "https://files.pythonhosted.org/packages/22/9b/deddfddae70651d058df6ca674790c4214d4021b59f22c3ae3a51ceb2551/PyQt5_sip-12.11.0-cp39-cp39-win32.whl", hash = "sha256:686071be054e5be6ca5aaaef7960931d4ba917277e839e2e978c7cbe3f43bb6e"},
+ {url = "https://files.pythonhosted.org/packages/25/e3/e869173f015fbff0a6764f002f5217ac2b8da2ae137b2792547bc81c65c0/PyQt5_sip-12.11.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f1f9e312ff8284d6dfebc5366f6f7d103f84eec23a4da0be0482403933e68660"},
+ {url = "https://files.pythonhosted.org/packages/29/f9/aed0c4194c1efe8840f658719efad248d10ee797278fecb23dcf236c52f3/PyQt5_sip-12.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:f6b72035da4e8fecbb0bc4a972e30a5674a9ad5608dbddaa517e983782dbf3bf"},
+ {url = "https://files.pythonhosted.org/packages/2e/1a/cf06313fb7d19d075d0185fcab5fc64ade7b8429788092fd1e3fd3c4e5bd/PyQt5_sip-12.11.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ec5e9ef78852e1f96f86d7e15c9215878422b83dde36d44f1539a3062942f19c"},
+ {url = "https://files.pythonhosted.org/packages/39/5f/fd9384fdcb9cd0388088899c110838007f49f5da1dd1ef6749bfb728a5da/PyQt5_sip-12.11.0.tar.gz", hash = "sha256:b4710fd85b57edef716cc55fae45bfd5bfac6fc7ba91036f1dcc3f331ca0eb39"},
+ {url = "https://files.pythonhosted.org/packages/41/b5/473fbe697cfe01b5389c968ba16bbcb6fa4080f594661ed44e8cc3ea5f7a/PyQt5_sip-12.11.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:4e5c1559311515291ea0ab0635529f14536954e3b973a7c7890ab7e4de1c2c23"},
+ {url = "https://files.pythonhosted.org/packages/53/32/af0a7d44f0abfceae185134efa4ef65770eeac20f21239bdce4aee0460bd/PyQt5_sip-12.11.0-cp311-cp311-win32.whl", hash = "sha256:43dfe6dd409e713edeb67019b85419a7a0dc9923bfc451d6cf3778471c122532"},
+ {url = "https://files.pythonhosted.org/packages/6e/8c/00fee4ad4602c3cbf4b73e1b42a0bba1018005633ec7ef831db33dbbd80a/PyQt5_sip-12.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:afa4ffffc54e306669bf2b66ea37abbc56c5cdda4f3f474d20324e3634302b12"},
+ {url = "https://files.pythonhosted.org/packages/71/47/a25ac7a064ab715a785bacd6d516e5787e56eaff19285fd0650d5d910a10/PyQt5_sip-12.11.0-cp37-cp37m-win32.whl", hash = "sha256:d12b81c3a08abf7657a2ebc7d3649852a1f327eb2146ebadf45930486d32e920"},
+ {url = "https://files.pythonhosted.org/packages/84/43/f64bb816feb9bb9e02d472092199635c243ae86ba12e77528dac05eab8db/PyQt5_sip-12.11.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ec1d8ce50be76c5c1d1c86c6dc0ccacc2446172dde98b663a17871f532f9bd44"},
+ {url = "https://files.pythonhosted.org/packages/c3/b6/da9a15efacb7b4a73c976e3fe13f9b6ff9694299093bc32d288ff89ddcc4/PyQt5_sip-12.11.0-cp310-cp310-win32.whl", hash = "sha256:ad21ca0ee8cae2a41b61fc04949dccfab6fe008749627d94e8c7078cb7a73af1"},
+ {url = "https://files.pythonhosted.org/packages/c7/8b/0f35a6e016c9f49da0cd8834ee2918e4d45dea449fba9ed1321d87826afd/PyQt5_sip-12.11.0-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:4031547dfb679be309094bfa79254f5badc5ddbe66b9ad38e319d84a7d612443"},
+ {url = "https://files.pythonhosted.org/packages/c8/62/cd0c7bab5ee60f8a4416857aa703bb7e285bf09d3b2564e136e55a5c3a79/PyQt5_sip-12.11.0-cp38-cp38-win32.whl", hash = "sha256:9bca450c5306890cb002fe36bbca18f979dd9e5b810b766dce8e3ce5e66ba795"},
+ {url = "https://files.pythonhosted.org/packages/d1/85/e30b11daf7b8d4e00ed51c17204ec3df446041206d22087d2c5dc17d8543/PyQt5_sip-12.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:42320e7a94b1085ed85d49794ed4ccfe86f1cae80b44a894db908a8aba2bc60e"},
+ {url = "https://files.pythonhosted.org/packages/d2/f9/7f62da7cc4a15d6a9c83a7d18b29ec9a6a04235fb9989973facbfbf51080/PyQt5_sip-12.11.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0f77655c62ec91d47c2c99143f248624d44dd2d8a12d016e7c020508ad418aca"},
+ {url = "https://files.pythonhosted.org/packages/ea/58/13becbb979991ea31ac08cdc7a1d3581b0ec9c4c85e76cb39c0ae645c720/PyQt5_sip-12.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:51e377789d59196213eddf458e6927f33ba9d217b614d17d20df16c9a8b2c41c"},
+ {url = "https://files.pythonhosted.org/packages/fa/88/d8846f019aa9b2f0e7f0a49e1ee9ba84ac200175d8856178b2167c8e770d/PyQt5_sip-12.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:3126c84568ab341c12e46ded2230f62a9a78752a70fdab13713f89a71cd44f73"},
+]
+"pyrsistent 0.19.2" = [
+ {url = "https://files.pythonhosted.org/packages/16/dd/17f6f147234f7c99b5a23fdcc318adac1c84ef5d179ce1bfe6afe19213a9/pyrsistent-0.19.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:879b4c2f4d41585c42df4d7654ddffff1239dc4065bc88b745f0341828b83e78"},
+ {url = "https://files.pythonhosted.org/packages/23/d9/3ffebba2a20084b56f8132ab1fa959161dcf6da4f27c85a4a4c9cc438db1/pyrsistent-0.19.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f1258f4e6c42ad0b20f9cfcc3ada5bd6b83374516cd01c0960e3cb75fdca6770"},
+ {url = "https://files.pythonhosted.org/packages/3c/55/666888db7c7070e4d64944ea2a786073e5a53aaaa67efdfe99a6d6c605b0/pyrsistent-0.19.2-cp37-cp37m-win_amd64.whl", hash = "sha256:3ba4134a3ff0fc7ad225b6b457d1309f4698108fb6b35532d015dca8f5abed73"},
+ {url = "https://files.pythonhosted.org/packages/42/c2/019693915e5d6fc88a145f4a4846fb4dc2a83cfa0a04256c76771eafb015/pyrsistent-0.19.2-cp310-cp310-win32.whl", hash = "sha256:456cb30ca8bff00596519f2c53e42c245c09e1a4543945703acd4312949bfd41"},
+ {url = "https://files.pythonhosted.org/packages/44/91/42f8dab8dbe3bb1f07409566085feeb237c28e9cf6e8957aa2377cad78c1/pyrsistent-0.19.2-cp39-cp39-win_amd64.whl", hash = "sha256:dec3eac7549869365fe263831f576c8457f6c833937c68542d08fde73457d291"},
+ {url = "https://files.pythonhosted.org/packages/54/d3/986fcfeaf62047840c571a857fb8f3ad1e9622081d5e7d0ee5e3451ca2e7/pyrsistent-0.19.2-py3-none-any.whl", hash = "sha256:ea6b79a02a28550c98b6ca9c35b9f492beaa54d7c5c9e9949555893c8a9234d0"},
+ {url = "https://files.pythonhosted.org/packages/54/d8/96308460ee237abf28b7798c491f8545a51e1c3e2f01895da4530c7bff86/pyrsistent-0.19.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:187d5730b0507d9285a96fca9716310d572e5464cadd19f22b63a6976254d77a"},
+ {url = "https://files.pythonhosted.org/packages/57/79/562894286bce4d1391abf4efbafa8a4a63ae482e1408ff08bf8a855c8358/pyrsistent-0.19.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e371b844cec09d8dc424d940e54bba8f67a03ebea20ff7b7b0d56f526c71d584"},
+ {url = "https://files.pythonhosted.org/packages/5e/f7/31748ccbbe68eb990347f252c4a39fcb1766e22907314922262a1dc5e1f6/pyrsistent-0.19.2-cp310-cp310-win_amd64.whl", hash = "sha256:b39725209e06759217d1ac5fcdb510e98670af9e37223985f330b611f62e7425"},
+ {url = "https://files.pythonhosted.org/packages/7d/f2/ad0363cb87673d3b076f6116c5d4385dc01fde8cce8292c7c7e133ba2fff/pyrsistent-0.19.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111156137b2e71f3a9936baf27cb322e8024dac3dc54ec7fb9f0bcf3249e68bb"},
+ {url = "https://files.pythonhosted.org/packages/8c/a8/bd70a21552061cc43e4ea9aa329990ca16af0ab747d3f8524d3778aba2f0/pyrsistent-0.19.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfd880614c6237243ff53a0539f1cb26987a6dc8ac6e66e0c5a40617296a045e"},
+ {url = "https://files.pythonhosted.org/packages/8f/2b/737a53eeb4b2036c58d8ac2e033590f1e4a2ffe7b174850aca4787a899a4/pyrsistent-0.19.2-cp37-cp37m-win32.whl", hash = "sha256:d690b18ac4b3e3cab73b0b7aa7dbe65978a172ff94970ff98d82f2031f8971c2"},
+ {url = "https://files.pythonhosted.org/packages/96/ad/a95f96af0f645ff74dc2145ab3d920c77a0d9d547dbaddb721c31bbeec53/pyrsistent-0.19.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d6982b5a0237e1b7d876b60265564648a69b14017f3b5f908c5be2de3f9abb7a"},
+ {url = "https://files.pythonhosted.org/packages/99/79/4ba4ef77bce2fb0c1140929e456a5101e5668af3aa46723e0a1daf2f437f/pyrsistent-0.19.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c43bec251bbd10e3cb58ced80609c5c1eb238da9ca78b964aea410fb820d00d6"},
+ {url = "https://files.pythonhosted.org/packages/a1/96/15848046e77dfe560f11099face554dc2e4b50f8b28d152be0ed72cd5a08/pyrsistent-0.19.2-cp39-cp39-win32.whl", hash = "sha256:71d332b0320642b3261e9fee47ab9e65872c2bd90260e5d225dabeed93cbd42b"},
+ {url = "https://files.pythonhosted.org/packages/a4/8d/7d7e504e14d1030e31e8ea656482e5fe5b556e11ce43ac899cf5ed1467f5/pyrsistent-0.19.2-cp38-cp38-win_amd64.whl", hash = "sha256:9cd3e9978d12b5d99cbdc727a3022da0430ad007dacf33d0bf554b96427f33ab"},
+ {url = "https://files.pythonhosted.org/packages/a5/55/165b1e579eeba5711f796362e151a4eba5a7bab6c307af68231b5d49c83f/pyrsistent-0.19.2-cp38-cp38-win32.whl", hash = "sha256:e5d8f84d81e3729c3b506657dddfe46e8ba9c330bf1858ee33108f8bb2adb38a"},
+ {url = "https://files.pythonhosted.org/packages/b8/ef/325da441a385a8a931b3eeb70db23cb52da42799691988d8d943c5237f10/pyrsistent-0.19.2.tar.gz", hash = "sha256:bfa0351be89c9fcbcb8c9879b826f4353be10f58f8a677efab0c017bf7137ec2"},
+ {url = "https://files.pythonhosted.org/packages/cd/2d/cf7bcdfcf5aba1a68ced5b32620e7c165d0491bd962d6cd1e78b1c1b2d7a/pyrsistent-0.19.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21455e2b16000440e896ab99e8304617151981ed40c29e9507ef1c2e4314ee95"},
+ {url = "https://files.pythonhosted.org/packages/d9/85/f175c246505fecf2ba073384e98188216b5f1547a82f8a69c58d78c698d3/pyrsistent-0.19.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a178209e2df710e3f142cbd05313ba0c5ebed0a55d78d9945ac7a4e09d923308"},
+ {url = "https://files.pythonhosted.org/packages/d9/ca/47cbb30a777aa74e4c40221385fb0c5eee5adfdb5406d29b4d10170dd243/pyrsistent-0.19.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:055ab45d5911d7cae397dc418808d8802fb95262751872c841c170b0dbf51eed"},
+ {url = "https://files.pythonhosted.org/packages/f6/3a/3cfb47a3cfd3993d0f57ba305ef71697d490b8e2efe8c3752bc5b0f321f4/pyrsistent-0.19.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2aede922a488861de0ad00c7630a6e2d57e8023e4be72d9d7147a9fcd2d30712"},
+ {url = "https://test-files.pythonhosted.org/packages/0a/59/b0a557f8868d0b0fd0838e4e8736bd3d855814ffc13edd1711c1bb53b00b/pyrsistent-0.19.2-cp310-cp310-win32.whl", hash = "sha256:366d5880bfea17db0701d30ed31d213f3bfb701041f1d3dca8f28c2f5b2e1b57"},
+ {url = "https://test-files.pythonhosted.org/packages/11/33/79dd36095faf26cc5283b6845476bf403cf3f9bc4632c8e1d67b21bca507/pyrsistent-0.19.2-cp37-cp37m-win_amd64.whl", hash = "sha256:355eb7ba5207b0083d6eac5277d1eb15d7c8ecff501789f90fa2cfefede36aa8"},
+ {url = "https://test-files.pythonhosted.org/packages/1e/d9/9fcb5f5c4a97c921e5564a971e34e3f64a78cbe01cc7851e309588371419/pyrsistent-0.19.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bd37861e2ba095ff93a0ae5e03c6ae69493ec6139f9d5612bc4bcc16050e636"},
+ {url = "https://test-files.pythonhosted.org/packages/23/a6/a6652b0d30e56c68a8ff083de3c9f3d4e1672de14d06356bf6bf9be72120/pyrsistent-0.19.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e673ad715330f2b77f43be88bd40682bb74f986dcd8ea517acc865b6968282dc"},
+ {url = "https://test-files.pythonhosted.org/packages/3e/73/3659176beb7e8e3b344659e239150a64ab6263637ed39cbf78da947dcff7/pyrsistent-0.19.2-cp37-cp37m-win32.whl", hash = "sha256:38e82add4c51dee3af852d687b17d606c8c9620288fbe5c2619ba1becd63bc89"},
+ {url = "https://test-files.pythonhosted.org/packages/40/71/ae801fa4edce35d6631678e683de898ebfaf218332068ecc6b880a03a4a9/pyrsistent-0.19.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:656a4ccad1c9bca66cde99fb1a3644cc7d404bb0dbcc46c7b069a9d2afa76764"},
+ {url = "https://test-files.pythonhosted.org/packages/54/41/2d741180d5848977e4594301db4dc37a6c357baf91399036b7b7ed85e87d/pyrsistent-0.19.2.tar.gz", hash = "sha256:3798f2578f7df1f073ad77fa0c75f28ad8ae389a3d4d491268e63618530900cc"},
+ {url = "https://test-files.pythonhosted.org/packages/57/42/c9a50e25af9dc14c8fb257786247728c1432e8ef2552dcb608b6b918a10a/pyrsistent-0.19.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9ff5be704ea20f4e50f82c3f34c19bb7cf62cd722c7a270211edbb299ab06864"},
+ {url = "https://test-files.pythonhosted.org/packages/57/e6/3fd3c54b417b9898ecbd33d4c487e167976204276efae873df2ed4d50b11/pyrsistent-0.19.2-cp38-cp38-win_amd64.whl", hash = "sha256:46123c3478c02a2369c20465a40e012e17a493629ab2dcba4f243adfffb4a5f5"},
+ {url = "https://test-files.pythonhosted.org/packages/58/07/5ad851368c22c10c198003b9f6346206a543cab8c45cfc4bfef6f80f4c31/pyrsistent-0.19.2-py3-none-any.whl", hash = "sha256:ce0eec0b2bff4374f45f065f51ba12362328dc6a94204b604974092506513477"},
+ {url = "https://test-files.pythonhosted.org/packages/64/c3/b68f450df9084480ef0220c719ddc0ff87477b5315c68020be23066b4445/pyrsistent-0.19.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46b9e70c4816fff946fbb845fc960a3c905a967d258b97d120f3e08eeba312f9"},
+ {url = "https://test-files.pythonhosted.org/packages/67/61/99dd9971507f3076486064fd8c9290c187a9278517f266aabccdef32775b/pyrsistent-0.19.2-cp310-cp310-win_amd64.whl", hash = "sha256:2cbb12fbbdad4bc06e4691b7e1b39149eb74e3b96cc64e9077e1c2563763756f"},
+ {url = "https://test-files.pythonhosted.org/packages/6e/43/a4cdf6d7febc0b1610a25b02c1477a934efa3a254c1e52879b1b9edcf266/pyrsistent-0.19.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5387688a245d7636e7c608376268fdd6713e686e1b38ce492dcbf015d18191ca"},
+ {url = "https://test-files.pythonhosted.org/packages/80/67/6dfc93f8335be5fafac8611b4b5417aa44f1092e3e20ad7d9238266381be/pyrsistent-0.19.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0add490da5d10f906cea2c3e131f8c86c842b021fb77037c451744afa67f00a4"},
+ {url = "https://test-files.pythonhosted.org/packages/90/f2/6f3c8b0ef92520bc929ec0c867003ef2c107d1b9dd706e1e8843b867e5f1/pyrsistent-0.19.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:982753d01827a54e982dee341c68469d1e357224fde5c60c8a32882ebec371ff"},
+ {url = "https://test-files.pythonhosted.org/packages/a2/0f/390999a7c47d46f14fbfea7a13d2d2460fa5caeec9f8da564638e5f1c533/pyrsistent-0.19.2-cp39-cp39-win32.whl", hash = "sha256:064da395ebb976c6489a785b3f2b6c7672e38294c450eb7618beb19d5396ca4c"},
+ {url = "https://test-files.pythonhosted.org/packages/a6/51/40812842800ac3efb5a662b81322634f6b09e6cb3fbdd14f25df1d1a004a/pyrsistent-0.19.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:445de06b3445903459621ca8da19aa560d8093bc0450bbb91d9e50f9e24ea66f"},
+ {url = "https://test-files.pythonhosted.org/packages/c5/7f/ebc7ff7c63331ce0f1973e6ead6ee866b40c83ec71caf0a98dcfb13fc697/pyrsistent-0.19.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b25f04d43e829c61bd67b7e424ac340112dfab1c710e45a5fec7ea044917523"},
+ {url = "https://test-files.pythonhosted.org/packages/da/af/b0e16e72699f06584a9187dfdb759a2372f2e5d7bc155db81562c033de87/pyrsistent-0.19.2-cp38-cp38-win32.whl", hash = "sha256:1a342d37e7bc9339ee23eba1fc5a3e0d18cdb8abc3302052fd18ecf21e4ebba3"},
+ {url = "https://test-files.pythonhosted.org/packages/ed/f2/3fcb6bc123bb0dbdd46c54a2c2bc2c8654e5f212e84a19b97750601d85e2/pyrsistent-0.19.2-cp39-cp39-win_amd64.whl", hash = "sha256:b51d9226e1d83f6aa5475405fe0ff244b7920dd2346766c5cc278bb0d5991863"},
+ {url = "https://test-files.pythonhosted.org/packages/ef/19/99ba519948e1c0a4625258770fc8ee025b4385a364f8856dc80075c50692/pyrsistent-0.19.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:475bcdd31a8751eddad1187da473f367254a9e5294a6c4746ea691511421086a"},
+ {url = "https://test-files.pythonhosted.org/packages/f5/b4/6c3c619372ab53a614f6ed3374e7fe52492f4deaf7580f316bdbc5fa1eea/pyrsistent-0.19.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e212ba610996f7f9c5b7925d7c6f4bba984adc6a3e9759fc600dd371ab30338f"},
+]
+"pytest 7.2.0" = [
+ {url = "https://files.pythonhosted.org/packages/0b/21/055f39bf8861580b43f845f9e8270c7786fe629b2f8562ff09007132e2e7/pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"},
+ {url = "https://files.pythonhosted.org/packages/67/68/a5eb36c3a8540594b6035e6cdae40c1ef1b6a2bfacbecc3d1a544583c078/pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"},
+]
+"pytest-cov 4.0.0" = [
+ {url = "https://files.pythonhosted.org/packages/ea/70/da97fd5f6270c7d2ce07559a19e5bf36a76f0af21500256f005a69d9beba/pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"},
+ {url = "https://files.pythonhosted.org/packages/fe/1f/9ec0ddd33bd2b37d6ec50bb39155bca4fe7085fa78b3b434c05459a860e3/pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"},
+]
+"pytz 2022.6" = [
+ {url = "https://files.pythonhosted.org/packages/76/63/1be349ff0a44e4795d9712cc0b2d806f5e063d4d34631b71b832fac715a8/pytz-2022.6.tar.gz", hash = "sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2"},
+ {url = "https://files.pythonhosted.org/packages/85/ac/92f998fc52a70afd7f6b788142632afb27cd60c8c782d1452b7466603332/pytz-2022.6-py2.py3-none-any.whl", hash = "sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427"},
+]
+"ragger 0.7.0" = [
+ {url = "https://test-files.pythonhosted.org/packages/09/e3/e96cbe8924818c956a3a250fa7f147989dac83575e4a4ffc7e60519da20d/ragger-0.7.0-py3-none-any.whl", hash = "sha256:7cd2ca1b53edd4c62ca2bb38861f6fa7f8faf4d67c1ec6b97de00dc8b0f97900"},
+ {url = "https://test-files.pythonhosted.org/packages/7a/90/539a89548e8fc77041c566ab59a971a3a7cebfe0f2e8b9289a34e09dd379/ragger-0.7.0.tar.gz", hash = "sha256:4a81a226a0f47017835b8c4074a4cae85f61dcad3c510364b12640c2897a94b0"},
+]
+"requests 2.28.1" = [
+ {url = "https://files.pythonhosted.org/packages/a5/61/a867851fd5ab77277495a8709ddda0861b28163c4613b011bc00228cc724/requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
+ {url = "https://files.pythonhosted.org/packages/ca/91/6d9b8ccacd0412c08820f72cebaa4f0c0441b5cda699c90f618b6f8a1b42/requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
+]
+"semver 2.13.0" = [
+ {url = "https://files.pythonhosted.org/packages/0b/70/b84f9944a03964a88031ef6ac219b6c91e8ba2f373362329d8770ef36f02/semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"},
+ {url = "https://files.pythonhosted.org/packages/31/a9/b61190916030ee9af83de342e101f192bbb436c59be20a4cb0cdb7256ece/semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"},
+ {url = "https://test-files.pythonhosted.org/packages/0b/70/b84f9944a03964a88031ef6ac219b6c91e8ba2f373362329d8770ef36f02/semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"},
+ {url = "https://test-files.pythonhosted.org/packages/31/a9/b61190916030ee9af83de342e101f192bbb436c59be20a4cb0cdb7256ece/semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"},
+]
+"setuptools 65.5.1" = [
+ {url = "https://files.pythonhosted.org/packages/26/f4/ca5cb6df512f453ad50f78900bf7ec6a5491ee44bb49d0f6f76802dbdd43/setuptools-65.5.1.tar.gz", hash = "sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f"},
+ {url = "https://files.pythonhosted.org/packages/b6/40/353c051f77ee5618adaf1fd96f4f6bae9714ed0a22c7142c01c24eb77fe4/setuptools-65.5.1-py3-none-any.whl", hash = "sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31"},
+]
+"six 1.16.0" = [
+ {url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
+ {url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
+]
+"speculos 0.1.157" = [
+ {url = "https://test-files.pythonhosted.org/packages/2b/0e/64fbe15430b4d0b978c71496f1eaccb00e50d88eb5fd8dd14c776111a21e/speculos-0.1.157-py3-none-any.whl", hash = "sha256:5e6284649675229e7a836d2fa8466054f378e636bdbbe1ddc25160b353bc1ebb"},
+ {url = "https://test-files.pythonhosted.org/packages/2d/63/5d6442d5e4249e71e792b23a76f83639051c26023881c589cc9acec4eda2/speculos-0.1.157.tar.gz", hash = "sha256:e432468cf8cc0c19a9b425d18384f56ef6b20e52a7478a6d0cc3122864b01a44"},
+]
+"tomli 2.0.1" = [
+ {url = "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
+ {url = "https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
+]
+"types-protobuf 3.20.4.5" = [
+ {url = "https://files.pythonhosted.org/packages/0a/e3/d80ef95536b29285096f2de61b83e4ef733eb2ef34cbcc4f9e8bd4b40ee1/types-protobuf-3.20.4.5.tar.gz", hash = "sha256:e9b45008d106e1d10cc77a29d2d344b85c0f01e2e643aaccf32f69e9e81b0cdd"},
+ {url = "https://files.pythonhosted.org/packages/92/02/cebbb618bf72fb671e9e0a710da26c959579a8964d8df79e1c60b3d38187/types_protobuf-3.20.4.5-py3-none-any.whl", hash = "sha256:97af5ce70d890fdb94cb0c906f5a6624ca2fef58bc04e27990a25509e992a950"},
+]
+"urllib3 1.26.12" = [
+ {url = "https://files.pythonhosted.org/packages/6f/de/5be2e3eed8426f871b170663333a0f627fc2924cc386cd41be065e7ea870/urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"},
+ {url = "https://files.pythonhosted.org/packages/b2/56/d87d6d3c4121c0bcec116919350ca05dc3afd2eeb7dc88d07e8083f8ea94/urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"},
+]
+"werkzeug 2.2.2" = [
+ {url = "https://files.pythonhosted.org/packages/c8/27/be6ddbcf60115305205de79c29004a0c6bc53cec814f733467b1bb89386d/Werkzeug-2.2.2-py3-none-any.whl", hash = "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5"},
+ {url = "https://files.pythonhosted.org/packages/f8/c1/1c8e539f040acd80f844c69a5ef8e2fccdf8b442dabb969e497b55d544e1/Werkzeug-2.2.2.tar.gz", hash = "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f"},
+ {url = "https://test-files.pythonhosted.org/packages/c8/27/be6ddbcf60115305205de79c29004a0c6bc53cec814f733467b1bb89386d/Werkzeug-2.2.2-py3-none-any.whl", hash = "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5"},
+ {url = "https://test-files.pythonhosted.org/packages/f8/c1/1c8e539f040acd80f844c69a5ef8e2fccdf8b442dabb969e497b55d544e1/Werkzeug-2.2.2.tar.gz", hash = "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f"},
+]
diff --git a/tests/proto b/tests/proto
new file mode 120000
index 00000000..5c8d3525
--- /dev/null
+++ b/tests/proto
@@ -0,0 +1 @@
+../proto
\ No newline at end of file
diff --git a/tests/pyproject.toml b/tests/pyproject.toml
new file mode 100644
index 00000000..faafe071
--- /dev/null
+++ b/tests/pyproject.toml
@@ -0,0 +1,26 @@
+[project]
+name = ""
+version = ""
+description = ""
+authors = [
+ {name = "", email = "chris@launchbadge.com"},
+]
+dependencies = [
+ "base58",
+ "bip32",
+ "ragger[speculos,tests]",
+ "protobuf==3.20.3",
+ "black>=22.10.0",
+ "mypy-protobuf>=3.3.0",
+]
+requires-python = ">=3.10"
+license = {text = "MIT"}
+
+[build-system]
+requires = ["pdm-pep517>=1.0.0"]
+build-backend = "pdm.pep517.api"
+
+[[tool.pdm.source]]
+url = "https://test.pypi.org/simple/"
+verify_ssl = true
+name = "PyPI Tests"
diff --git a/tests/requirements.txt b/tests/requirements.txt
new file mode 100644
index 00000000..1151f759
--- /dev/null
+++ b/tests/requirements.txt
@@ -0,0 +1,4 @@
+base58
+bip32
+ragger[tests,speculos]
+protobuf==3.20.3
diff --git a/tests/test_hedera.py b/tests/test_hedera.py
new file mode 100644
index 00000000..bbf26221
--- /dev/null
+++ b/tests/test_hedera.py
@@ -0,0 +1,476 @@
+from ragger.backend.interface import RAPDU, RaisePolicy
+
+from .apps.hedera import HederaClient, ErrorType
+from .apps.hedera_builder import crypto_create_account_conf
+from .apps.hedera_builder import crypto_create_account_stake_account_conf
+from .apps.hedera_builder import crypto_create_account_stake_node_conf
+from .apps.hedera_builder import crypto_create_account_stake_toggle_rewards_conf
+from .apps.hedera_builder import account_update_conf
+from .apps.hedera_builder import crypto_transfer_token_conf
+from .apps.hedera_builder import crypto_transfer_hbar_conf
+from .apps.hedera_builder import crypto_transfer_verify
+from .apps.hedera_builder import token_associate_conf
+from .apps.hedera_builder import token_dissociate_conf
+from .apps.hedera_builder import token_burn_conf
+from .apps.hedera_builder import token_mint_conf
+
+
+def test_hedera_get_public_key_ok(client, firmware):
+ hedera = HederaClient(client)
+ values = [
+ (0, "78be747e6894ee5f965e3fb0e4c1628af2f9ae0d94dc01d9b9aab75484c3184b"),
+ (11095, "644ef690d394e8140fa278273913425bc83c59067a392a9e7f703ead4973caf8"),
+ (294967295, "02357008e57f96bb250f789c63eb3a241c1eae034d461468b76b8174a59bdc9b"),
+ (
+ 2294967295,
+ "2cbd40ac0a3e25a315aed7e211fd0056127075dfa4ba1717a7a047a2030b5efb",
+ ),
+ ]
+ for (index, key) in values:
+ from_public_key = hedera.get_public_key_non_confirm(index).data
+ assert from_public_key.hex() == key
+ with hedera.get_public_key_confirm(index):
+ if firmware.device == "nanos":
+ hedera.validate()
+ else:
+ hedera.validate_screen(1)
+
+ from_public_key = hedera.get_async_response().data
+ assert from_public_key.hex() == key
+
+
+def test_hedera_get_public_key_refused(client, firmware):
+ hedera = HederaClient(client)
+ with hedera.get_public_key_confirm(0):
+ client.raise_policy = RaisePolicy.RAISE_NOTHING
+ if firmware.device == "nanos":
+ hedera.refuse()
+ else:
+ hedera.validate_screen(1 + 1)
+
+ rapdu = hedera.get_async_response()
+ assert rapdu.status == ErrorType.EXCEPTION_USER_REJECTED
+
+
+def test_hedera_crypto_create_account_ok(client, firmware):
+ hedera = HederaClient(client)
+ conf = crypto_create_account_conf(initialBalance=5)
+ with hedera.send_sign_transaction(
+ index=0,
+ operator_shard_num=1,
+ operator_realm_num=2,
+ operator_account_num=3,
+ transaction_fee=5,
+ memo="this_is_the_memo",
+ conf=conf,
+ ):
+ hedera.validate_screen(7)
+
+
+def test_hedera_crypto_create_account_refused(client, firmware):
+ hedera = HederaClient(client)
+ conf = crypto_create_account_conf(initialBalance=5)
+ with hedera.send_sign_transaction(
+ index=0,
+ operator_shard_num=1,
+ operator_realm_num=2,
+ operator_account_num=3,
+ transaction_fee=5,
+ memo="this_is_the_memo",
+ conf=conf,
+ ):
+ client.raise_policy = RaisePolicy.RAISE_NOTHING
+ hedera.validate_screen(8)
+
+ rapdu = hedera.get_async_response()
+ assert rapdu.status == ErrorType.EXCEPTION_USER_REJECTED
+
+
+def test_hedera_crypto_create_account_stake_account(client, firmware):
+ hedera = HederaClient(client)
+ conf = crypto_create_account_stake_account_conf()
+ with hedera.send_sign_transaction(
+ index=0,
+ operator_shard_num=1,
+ operator_realm_num=2,
+ operator_account_num=3,
+ transaction_fee=5,
+ memo="this_is_the_memo",
+ conf=conf,
+ ):
+ hedera.validate_screen(7)
+
+
+def test_hedera_crypto_create_account_stake_node(client, firmware):
+ hedera = HederaClient(client)
+ conf = crypto_create_account_stake_node_conf()
+ with hedera.send_sign_transaction(
+ index=0,
+ operator_shard_num=1,
+ operator_realm_num=2,
+ operator_account_num=3,
+ transaction_fee=5,
+ memo="this_is_the_memo",
+ conf=conf,
+ ):
+ hedera.validate_screen(7)
+
+
+def test_hedera_crypto_create_account_stake_toggle_rewards(client, firmware):
+ hedera = HederaClient(client)
+ conf = crypto_create_account_stake_toggle_rewards_conf()
+ with hedera.send_sign_transaction(
+ index=0,
+ operator_shard_num=1,
+ operator_realm_num=2,
+ operator_account_num=3,
+ transaction_fee=5,
+ memo="this_is_the_memo",
+ conf=conf,
+ ):
+ hedera.validate_screen(7)
+
+
+def test_hedera_account_update(client, firmware):
+ hedera = HederaClient(client)
+ conf = account_update_conf()
+ with hedera.send_sign_transaction(
+ index=0,
+ operator_shard_num=1,
+ operator_realm_num=2,
+ operator_account_num=3,
+ transaction_fee=5,
+ memo="this_is_the_memo",
+ conf=conf,
+ ):
+ hedera.validate_screen(7)
+
+
+def test_hedera_transfer_token_ok(client, firmware):
+ hedera = HederaClient(client)
+ conf = crypto_transfer_token_conf(
+ token_shardNum=15,
+ token_realmNum=16,
+ token_tokenNum=17,
+ sender_shardNum=57,
+ sender_realmNum=58,
+ sender_accountNum=59,
+ recipient_shardNum=100,
+ recipient_realmNum=101,
+ recipient_accountNum=102,
+ amount=1234567890,
+ decimals=9,
+ )
+
+ with hedera.send_sign_transaction(
+ index=0,
+ operator_shard_num=1,
+ operator_realm_num=2,
+ operator_account_num=3,
+ transaction_fee=5,
+ memo="this_is_the_memo",
+ conf=conf,
+ ):
+ hedera.validate_screen(7)
+
+
+def test_hedera_transfer_token_refused(client, firmware):
+ hedera = HederaClient(client)
+ conf = crypto_transfer_token_conf(
+ token_shardNum=15,
+ token_realmNum=16,
+ token_tokenNum=17,
+ sender_shardNum=57,
+ sender_realmNum=58,
+ sender_accountNum=59,
+ recipient_shardNum=100,
+ recipient_realmNum=101,
+ recipient_accountNum=102,
+ amount=1234567890,
+ decimals=9,
+ )
+
+ with hedera.send_sign_transaction(
+ index=0,
+ operator_shard_num=1,
+ operator_realm_num=2,
+ operator_account_num=3,
+ transaction_fee=5,
+ memo="this_is_the_memo",
+ conf=conf,
+ ):
+ client.raise_policy = RaisePolicy.RAISE_NOTHING
+ hedera.validate_screen(7 + 1)
+
+ rapdu = hedera.get_async_response()
+ assert rapdu.status == ErrorType.EXCEPTION_USER_REJECTED
+
+
+def test_hedera_transfer_hbar_ok(client, firmware):
+ hedera = HederaClient(client)
+ conf = crypto_transfer_hbar_conf(
+ sender_shardNum=57,
+ sender_realmNum=58,
+ sender_accountNum=59,
+ recipient_shardNum=100,
+ recipient_realmNum=101,
+ recipient_accountNum=102,
+ amount=1234567890,
+ )
+
+ with hedera.send_sign_transaction(
+ index=0,
+ operator_shard_num=1,
+ operator_realm_num=2,
+ operator_account_num=3,
+ transaction_fee=5,
+ memo="this_is_the_memo",
+ conf=conf,
+ ):
+ hedera.validate_screen(7)
+
+
+def test_hedera_transfer_hbar_refused(client, firmware):
+ hedera = HederaClient(client)
+ conf = crypto_transfer_hbar_conf(
+ sender_shardNum=57,
+ sender_realmNum=58,
+ sender_accountNum=59,
+ recipient_shardNum=100,
+ recipient_realmNum=101,
+ recipient_accountNum=102,
+ amount=1234567890,
+ )
+
+ with hedera.send_sign_transaction(
+ index=0,
+ operator_shard_num=1,
+ operator_realm_num=2,
+ operator_account_num=3,
+ transaction_fee=5,
+ memo="this_is_the_memo",
+ conf=conf,
+ ):
+ client.raise_policy = RaisePolicy.RAISE_NOTHING
+ hedera.validate_screen(7 + 1)
+
+ rapdu = hedera.get_async_response()
+ assert rapdu.status == ErrorType.EXCEPTION_USER_REJECTED
+
+
+def test_hedera_token_associate_ok(client, firmware):
+ hedera = HederaClient(client)
+ conf = token_associate_conf(
+ token_shardNum=57,
+ token_realmNum=58,
+ token_tokenNum=59,
+ sender_shardNum=100,
+ sender_realmNum=101,
+ sender_accountNum=102,
+ )
+
+ with hedera.send_sign_transaction(
+ index=0,
+ operator_shard_num=1,
+ operator_realm_num=2,
+ operator_account_num=3,
+ transaction_fee=5,
+ memo="this_is_the_memo",
+ conf=conf,
+ ):
+ hedera.validate_screen(6)
+
+
+def test_hedera_token_associate_refused(client, firmware):
+ hedera = HederaClient(client)
+ conf = token_associate_conf(
+ token_shardNum=57,
+ token_realmNum=58,
+ token_tokenNum=59,
+ sender_shardNum=100,
+ sender_realmNum=101,
+ sender_accountNum=102,
+ )
+
+ with hedera.send_sign_transaction(
+ index=0,
+ operator_shard_num=1,
+ operator_realm_num=2,
+ operator_account_num=3,
+ transaction_fee=5,
+ memo="this_is_the_memo",
+ conf=conf,
+ ):
+ client.raise_policy = RaisePolicy.RAISE_NOTHING
+ hedera.validate_screen(7)
+
+ rapdu = hedera.get_async_response()
+ assert rapdu.status == ErrorType.EXCEPTION_USER_REJECTED
+
+
+def test_hedera_token_dissociate_ok(client, firmware):
+ hedera = HederaClient(client)
+ conf = token_dissociate_conf(
+ token_shardNum=57,
+ token_realmNum=58,
+ token_tokenNum=59,
+ sender_shardNum=666,
+ sender_realmNum=666,
+ sender_accountNum=666,
+ )
+ with hedera.send_sign_transaction(
+ index=0,
+ operator_shard_num=1,
+ operator_realm_num=2,
+ operator_account_num=3,
+ transaction_fee=5,
+ memo="this_is_the_memo",
+ conf=conf,
+ ):
+ hedera.validate_screen(6)
+
+
+def test_hedera_token_dissoicate_refused(client, firmware):
+ hedera = HederaClient(client)
+ conf = token_dissociate_conf(
+ token_shardNum=57,
+ token_realmNum=58,
+ token_tokenNum=59,
+ sender_shardNum=666,
+ sender_realmNum=666,
+ sender_accountNum=666,
+ )
+ with hedera.send_sign_transaction(
+ index=0,
+ operator_shard_num=1,
+ operator_realm_num=2,
+ operator_account_num=3,
+ transaction_fee=5,
+ memo="this_is_the_memo",
+ conf=conf,
+ ):
+ client.raise_policy = RaisePolicy.RAISE_NOTHING
+ hedera.validate_screen(7)
+
+ rapdu = hedera.get_async_response()
+ assert rapdu.status == ErrorType.EXCEPTION_USER_REJECTED
+
+
+def test_hedera_token_burn_ok(client, firmware):
+ hedera = HederaClient(client)
+ conf = token_burn_conf(
+ token_shardNum=57, token_realmNum=58, token_tokenNum=59, amount=7745309389
+ )
+
+ with hedera.send_sign_transaction(
+ index=0,
+ operator_shard_num=1,
+ operator_realm_num=2,
+ operator_account_num=3,
+ transaction_fee=5,
+ memo="this_is_the_memo",
+ conf=conf,
+ ):
+ hedera.validate_screen(6)
+
+
+def test_hedera_token_burn_refused(client, firmware):
+ hedera = HederaClient(client)
+ conf = token_burn_conf(
+ token_shardNum=57, token_realmNum=58, token_tokenNum=59, amount=7745309389
+ )
+
+ with hedera.send_sign_transaction(
+ index=0,
+ operator_shard_num=1,
+ operator_realm_num=2,
+ operator_account_num=3,
+ transaction_fee=5,
+ memo="this_is_the_memo",
+ conf=conf,
+ ):
+ client.raise_policy = RaisePolicy.RAISE_NOTHING
+ hedera.validate_screen(7)
+
+ rapdu = hedera.get_async_response()
+ assert rapdu.status == ErrorType.EXCEPTION_USER_REJECTED
+
+
+def test_hedera_token_mint_ok(client, firmware):
+ hedera = HederaClient(client)
+ conf = token_mint_conf(
+ token_shardNum=57, token_realmNum=58, token_tokenNum=59, amount=7745309389
+ )
+
+ with hedera.send_sign_transaction(
+ index=0,
+ operator_shard_num=1,
+ operator_realm_num=2,
+ operator_account_num=3,
+ transaction_fee=5,
+ memo="this_is_the_memo",
+ conf=conf,
+ ):
+ hedera.validate_screen(6)
+
+
+def test_hedera_token_mint_refused(client, firmware):
+ hedera = HederaClient(client)
+ conf = token_mint_conf(
+ token_shardNum=57, token_realmNum=58, token_tokenNum=59, amount=7745309389
+ )
+
+ with hedera.send_sign_transaction(
+ index=0,
+ operator_shard_num=1,
+ operator_realm_num=2,
+ operator_account_num=3,
+ transaction_fee=5,
+ memo="this_is_the_memo",
+ conf=conf,
+ ):
+ client.raise_policy = RaisePolicy.RAISE_NOTHING
+ hedera.validate_screen(7)
+
+ rapdu = hedera.get_async_response()
+ assert rapdu.status == ErrorType.EXCEPTION_USER_REJECTED
+
+
+def test_hedera_transfer_verify_ok(client, firmware):
+ hedera = HederaClient(client)
+ conf = crypto_transfer_verify(
+ sender_shardNum=57, sender_realmNum=58, sender_accountNum=59
+ )
+
+ with hedera.send_sign_transaction(
+ index=0,
+ operator_shard_num=1,
+ operator_realm_num=2,
+ operator_account_num=3,
+ transaction_fee=1,
+ memo="this_is_the_memo",
+ conf=conf,
+ ):
+ hedera.validate_screen(2)
+
+
+def test_hedera_transfer_verify_refused(client, firmware):
+ hedera = HederaClient(client)
+ conf = crypto_transfer_verify(
+ sender_shardNum=57, sender_realmNum=58, sender_accountNum=59
+ )
+
+ with hedera.send_sign_transaction(
+ index=0,
+ operator_shard_num=1,
+ operator_realm_num=2,
+ operator_account_num=3,
+ transaction_fee=1,
+ memo="this_is_the_memo",
+ conf=conf,
+ ):
+ client.raise_policy = RaisePolicy.RAISE_NOTHING
+ hedera.validate_screen(3)
+
+ rapdu = hedera.get_async_response()
+ assert rapdu.status == ErrorType.EXCEPTION_USER_REJECTED
diff --git a/tests/utils.py b/tests/utils.py
new file mode 100644
index 00000000..88cc0973
--- /dev/null
+++ b/tests/utils.py
@@ -0,0 +1,57 @@
+from typing import Optional, Tuple
+from pathlib import Path
+from bip32 import HARDENED_INDEX
+from enum import IntEnum
+
+from ragger.backend import SpeculosBackend
+
+
+def app_path_from_app_name(app_dir, app_name: str, device: str) -> Path:
+ assert app_dir.is_dir(), f"{app_dir} is not a directory"
+ app_path = app_dir / (app_name + "_" + device + ".elf")
+ assert app_path.is_file(), f"{app_path} must exist"
+ return app_path
+
+def prefix_with_len(to_prefix: bytes) -> bytes:
+ return len(to_prefix).to_bytes(1, byteorder="big") + to_prefix
+
+def validate_displayed_message(client: SpeculosBackend, num_screen_skip: int):
+ for _ in range(num_screen_skip):
+ client.right_click()
+ client.both_click()
+
+# DERIVATION PATHS CALCULATIONS
+
+class BtcDerivationPathFormat(IntEnum):
+ LEGACY = 0x00
+ P2SH = 0x01
+ BECH32 = 0x02
+ CASHADDR = 0x03 # Deprecated
+ BECH32M = 0x04
+
+def pack_derivation_path(derivation_path: str) -> bytes:
+ split = derivation_path.split("/")
+ assert split.pop(0) == "m", "master expected"
+ derivation_path: bytes = (len(split)).to_bytes(1, byteorder='big')
+ for i in split:
+ if (i[-1] == "'"):
+ derivation_path += (int(i[:-1]) | HARDENED_INDEX).to_bytes(4, byteorder='big')
+ else:
+ derivation_path += int(i).to_bytes(4, byteorder='big')
+ return derivation_path
+
+def bitcoin_pack_derivation_path(format: BtcDerivationPathFormat, derivation_path: str) -> bytes:
+ assert isinstance(format, BtcDerivationPathFormat)
+ return format.to_bytes(1, "big") + pack_derivation_path(derivation_path)
+
+
+# CURRENCY CONFIG CALCULATIONS
+
+def create_currency_config(main_ticker: str, application_name: str, sub_coin_config: Optional[Tuple[str, int]] = None) -> bytes:
+ sub_config: bytes = b""
+ if sub_coin_config is not None:
+ sub_config = prefix_with_len(sub_coin_config[0].encode()) + sub_coin_config[1].to_bytes(1, byteorder="big")
+ coin_config: bytes = b""
+ for element in [main_ticker.encode(), application_name.encode(), sub_config]:
+ coin_config += prefix_with_len(element)
+ return coin_config