Skip to content

Commit 4119e05

Browse files
Dosik130xivanov
andauthored
feat: add FileCreateTransaction (#147)
* feat: add FileId class Signed-off-by: dosi <[email protected]> * feat: add unit tests for FileId Signed-off-by: dosi <[email protected]> * feat: implement FileCreateTransaction Signed-off-by: dosi <[email protected]> * test: add unit tests for file create Signed-off-by: dosi <[email protected]> * test: add integration tests for file create Signed-off-by: dosi <[email protected]> * docs: add FileCreateTransaction docs in examples README Signed-off-by: dosi <[email protected]> * docs: add example for file create Signed-off-by: dosi <[email protected]> * chore: add FileCreateTransaction in __init__.py Signed-off-by: dosi <[email protected]> * docs: update docstring in FileCreateTransaction Signed-off-by: dosi <[email protected]> * test: fix unit tests Signed-off-by: dosi <[email protected]> * chore: address PR feedback Signed-off-by: Ivan Ivanov <[email protected]> * test: fix Signed-off-by: Ivan Ivanov <[email protected]> * fix: address PR feedback Signed-off-by: Ivan Ivanov <[email protected]> --------- Signed-off-by: dosi <[email protected]> Signed-off-by: Ivan Ivanov <[email protected]> Co-authored-by: Ivan Ivanov <[email protected]>
1 parent f6864df commit 4119e05

File tree

9 files changed

+772
-1
lines changed

9 files changed

+772
-1
lines changed

examples/README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ You can choose either syntax or even mix both styles in your projects.
4646
- [Deleting a Topic](#deleting-a-topic)
4747
- [Querying Topic](#querying-topic)
4848
- [Querying Topic Message](#querying-topic-message)
49+
- [File Transactions](#file-transactions)
50+
- [Creating a File](#creating-a-file)
4951
- [Miscellaneous Queries](#miscellaneous-queries)
5052
- [Querying Transaction Record](#querying-transaction-record)
5153

@@ -908,6 +910,35 @@ query = (
908910
query.subscribe(client)
909911
```
910912

913+
## File Transactions
914+
915+
### Creating a File
916+
917+
#### Pythonic Syntax:
918+
```
919+
transaction = FileCreateTransaction(
920+
keys=[account_public_key],
921+
contents=file_contents,
922+
file_memo="My first file on Hedera"
923+
).freeze_with(client)
924+
925+
transaction.sign(account_private_key)
926+
transaction.execute(client)
927+
```
928+
929+
#### Method Chaining:
930+
```
931+
transaction = (
932+
FileCreateTransaction()
933+
.set_keys(account_public_key)
934+
.set_contents(file_contents)
935+
.set_file_memo("My first file on Hedera")
936+
.freeze_with(client)
937+
.sign(account_private_key)
938+
)
939+
940+
transaction.execute(client)
941+
911942
## Miscellaneous Queries
912943
913944
### Querying Transaction Record

examples/file_create.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import os
2+
import sys
3+
from dotenv import load_dotenv
4+
5+
from hiero_sdk_python import (
6+
Client,
7+
AccountId,
8+
PrivateKey,
9+
Network,
10+
)
11+
from hiero_sdk_python.file.file_create_transaction import FileCreateTransaction
12+
from hiero_sdk_python.response_code import ResponseCode
13+
14+
load_dotenv()
15+
16+
def setup_client():
17+
"""Initialize and set up the client with operator account"""
18+
network = Network(network='testnet')
19+
client = Client(network)
20+
21+
operator_id = AccountId.from_string(os.getenv('OPERATOR_ID'))
22+
operator_key = PrivateKey.from_string(os.getenv('OPERATOR_KEY'))
23+
client.set_operator(operator_id, operator_key)
24+
25+
return client
26+
27+
def file_create():
28+
"""
29+
Demonstrates creating a file on the network by:
30+
1. Setting up client with operator account
31+
2. Creating a file with a private key
32+
3. Creating a new file
33+
"""
34+
client = setup_client()
35+
36+
file_private_key = PrivateKey.generate_ed25519()
37+
38+
# Create file
39+
receipt = (
40+
FileCreateTransaction()
41+
.set_keys(file_private_key.public_key()) # Set the keys required to sign any modifications to this file
42+
.set_contents(b"Hello, this is the content of my file on Hedera!")
43+
.set_file_memo("My first file on Hedera")
44+
.freeze_with(client)
45+
.sign(file_private_key)
46+
.execute(client)
47+
)
48+
49+
# Check if the transaction was successful
50+
if receipt.status != ResponseCode.SUCCESS:
51+
print(f"File creation failed with status: {ResponseCode(receipt.status).name}")
52+
sys.exit(1)
53+
54+
file_id = receipt.fileId
55+
print(f"File created successfully with ID: {file_id}")
56+
57+
if __name__ == "__main__":
58+
file_create()

src/hiero_sdk_python/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@
7979
from .logger.logger import Logger
8080
from .logger.log_level import LogLevel
8181

82+
# File
83+
from .file.file_create_transaction import FileCreateTransaction
8284

8385
__all__ = [
8486
# Client
@@ -156,5 +158,8 @@
156158
"Hbar",
157159
"ResponseCode",
158160
"Timestamp",
159-
"Duration"
161+
"Duration",
162+
163+
# File
164+
"FileCreateTransaction",
160165
]
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import time
2+
from typing import Optional
3+
from hiero_sdk_python.crypto.public_key import PublicKey
4+
from hiero_sdk_python.hapi.services import file_create_pb2
5+
from hiero_sdk_python.hapi.services.basic_types_pb2 import KeyList as KeyListProto
6+
from hiero_sdk_python.hbar import Hbar
7+
from hiero_sdk_python.timestamp import Timestamp
8+
from hiero_sdk_python.transaction.transaction import Transaction
9+
from hiero_sdk_python.channels import _Channel
10+
from hiero_sdk_python.executable import _Method
11+
12+
class FileCreateTransaction(Transaction):
13+
"""
14+
Represents a file create transaction on the network.
15+
16+
This transaction creates a new file on the network with the specified keys, contents,
17+
expiration time and memo.
18+
19+
Inherits from the base Transaction class and implements the required methods
20+
to build and execute a file create transaction.
21+
"""
22+
23+
# 90 days in seconds is the default expiration time
24+
DEFAULT_EXPIRY_SECONDS = 90 * 24 * 60 * 60 # 7776000
25+
26+
def __init__(self, keys: Optional[list[PublicKey]] = None, contents: Optional[str | bytes] = None, expiration_time: Optional[Timestamp] = None, file_memo: Optional[str] = None):
27+
"""
28+
Initializes a new FileCreateTransaction instance with the specified parameters.
29+
30+
Args:
31+
keys (Optional[list[PublicKey]], optional): The keys that are allowed to update/delete the file.
32+
contents (Optional[str | bytes], optional): The contents of the file to create. Strings will be automatically encoded as UTF-8 bytes.
33+
expiration_time (Optional[Timestamp], optional): The time at which the file should expire.
34+
file_memo (Optional[str], optional): A memo to include with the file.
35+
"""
36+
super().__init__()
37+
self.keys: Optional[list[PublicKey]] = keys or []
38+
self.contents: Optional[bytes] = self._encode_contents(contents)
39+
self.expiration_time: Optional[Timestamp] = expiration_time if expiration_time else Timestamp(int(time.time()) + self.DEFAULT_EXPIRY_SECONDS, 0)
40+
self.file_memo: Optional[str] = file_memo
41+
self._default_transaction_fee = Hbar(5).to_tinybars()
42+
43+
def _encode_contents(self, contents: Optional[str | bytes]) -> Optional[bytes]:
44+
"""
45+
Helper method to encode string contents to UTF-8 bytes.
46+
47+
Args:
48+
contents (Optional[str | bytes]): The contents to encode.
49+
50+
Returns:
51+
Optional[bytes]: The encoded contents or None if input is None.
52+
"""
53+
if contents is None:
54+
return None
55+
if isinstance(contents, str):
56+
return contents.encode('utf-8')
57+
return contents
58+
59+
def set_keys(self, keys: Optional[list[PublicKey]] | PublicKey) -> 'FileCreateTransaction':
60+
"""
61+
Sets the keys for this file create transaction.
62+
63+
Args:
64+
keys (Optional[list[PublicKey]] | PublicKey): The keys to set for the file. Can be a list of PublicKey objects or None.
65+
66+
Returns:
67+
FileCreateTransaction: This transaction instance.
68+
"""
69+
self._require_not_frozen()
70+
if isinstance(keys, PublicKey):
71+
self.keys = [keys]
72+
else:
73+
self.keys = keys or []
74+
return self
75+
76+
def set_contents(self, contents: Optional[str | bytes]) -> 'FileCreateTransaction':
77+
"""
78+
Sets the contents for this file create transaction.
79+
80+
Args:
81+
contents (Optional[str | bytes]): The contents of the file to create. Strings will be automatically encoded as UTF-8 bytes.
82+
83+
Returns:
84+
FileCreateTransaction: This transaction instance.
85+
"""
86+
self._require_not_frozen()
87+
self.contents = self._encode_contents(contents)
88+
return self
89+
90+
def set_expiration_time(self, expiration_time: Optional[Timestamp]) -> 'FileCreateTransaction':
91+
"""
92+
Sets the expiration time for this file create transaction.
93+
94+
Args:
95+
expiration_time (Optional[Timestamp]): The expiration time for the file.
96+
97+
Returns:
98+
FileCreateTransaction: This transaction instance.
99+
"""
100+
self._require_not_frozen()
101+
self.expiration_time = expiration_time
102+
return self
103+
104+
def set_file_memo(self, file_memo: Optional[str]) -> 'FileCreateTransaction':
105+
"""
106+
Sets the memo for this file create transaction.
107+
108+
Args:
109+
file_memo (Optional[str]): The memo to set for the file.
110+
111+
Returns:
112+
FileCreateTransaction: This transaction instance.
113+
"""
114+
self._require_not_frozen()
115+
self.file_memo = file_memo
116+
return self
117+
118+
def build_transaction_body(self):
119+
"""
120+
Builds the transaction body for this file create transaction.
121+
122+
Returns:
123+
TransactionBody: The built transaction body.
124+
"""
125+
file_create_body = file_create_pb2.FileCreateTransactionBody(
126+
keys=KeyListProto(keys=[key._to_proto() for key in self.keys or []]),
127+
contents=self.contents if self.contents is not None else b'',
128+
expirationTime=self.expiration_time._to_protobuf() if self.expiration_time else None,
129+
memo=self.file_memo if self.file_memo is not None else ''
130+
)
131+
transaction_body = self.build_base_transaction_body()
132+
transaction_body.fileCreate.CopyFrom(file_create_body)
133+
return transaction_body
134+
135+
def _get_method(self, channel: _Channel) -> _Method:
136+
"""
137+
Gets the method to execute the file create transaction.
138+
139+
This internal method returns a _Method object containing the appropriate gRPC
140+
function to call when executing this transaction on the Hedera network.
141+
142+
Args:
143+
channel (_Channel): The channel containing service stubs
144+
145+
Returns:
146+
_Method: An object containing the transaction function to create a file.
147+
"""
148+
return _Method(
149+
transaction_func=channel.file.createFile,
150+
query_func=None
151+
)
152+
153+
def _from_proto(self, proto: file_create_pb2.FileCreateTransactionBody) -> 'FileCreateTransaction':
154+
"""
155+
Initializes a new FileCreateTransaction instance from a protobuf object.
156+
157+
Args:
158+
proto (FileCreateTransactionBody): The protobuf object to initialize from.
159+
160+
Returns:
161+
FileCreateTransaction: This transaction instance.
162+
"""
163+
self.keys = [PublicKey._from_proto(key) for key in proto.keys.keys] if proto.keys.keys else []
164+
self.contents = proto.contents
165+
self.expiration_time = Timestamp._from_protobuf(proto.expirationTime) if proto.expirationTime else None
166+
self.file_memo = proto.memo
167+
return self
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from dataclasses import dataclass
2+
3+
from hiero_sdk_python.hapi.services import basic_types_pb2
4+
5+
6+
@dataclass
7+
class FileId:
8+
"""
9+
Represents a file ID on the network.
10+
11+
A file ID consists of three components: shard, realm, and file (number).
12+
These components uniquely identify a file in the network.
13+
14+
Attributes:
15+
shard (int): The shard number. Defaults to 0.
16+
realm (int): The realm number. Defaults to 0.
17+
file (int): The file number. Defaults to 0.
18+
"""
19+
shard: int = 0
20+
realm: int = 0
21+
file: int = 0
22+
23+
@classmethod
24+
def _from_proto(cls, file_id_proto: basic_types_pb2.FileID) -> 'FileId':
25+
"""
26+
Creates a FileId instance from a protobuf FileID object.
27+
"""
28+
return cls(
29+
shard=file_id_proto.shardNum,
30+
realm=file_id_proto.realmNum,
31+
file=file_id_proto.fileNum
32+
)
33+
34+
def _to_proto(self) -> basic_types_pb2.FileID:
35+
"""
36+
Converts the FileId instance to a protobuf FileID object.
37+
"""
38+
return basic_types_pb2.FileID(
39+
shardNum=self.shard,
40+
realmNum=self.realm,
41+
fileNum=self.file
42+
)
43+
44+
@classmethod
45+
def from_string(cls, file_id_str: str) -> 'FileId':
46+
"""
47+
Creates a FileId instance from a string in the format 'shard.realm.file'.
48+
"""
49+
parts = file_id_str.strip().split('.')
50+
if len(parts) != 3:
51+
raise ValueError("Invalid file ID string format. Expected 'shard.realm.file'")
52+
shard, realm, file = map(int, parts)
53+
return cls(shard, realm, file)
54+
55+
def __str__(self) -> str:
56+
"""
57+
Returns a string representation of the FileId instance.
58+
"""
59+
return f"{self.shard}.{self.realm}.{self.file}"

src/hiero_sdk_python/transaction/transaction_receipt.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from hiero_sdk_python.file.file_id import FileId
12
from hiero_sdk_python.tokens.token_id import TokenId
23
from hiero_sdk_python.consensus.topic_id import TopicId
34
from hiero_sdk_python.account.account_id import AccountId
@@ -75,6 +76,16 @@ def serial_numbers(self):
7576
"""
7677
return self._receipt_proto.serialNumbers
7778

79+
@property
80+
def fileId(self):
81+
"""
82+
Returns the file ID associated with this receipt.
83+
"""
84+
if self._receipt_proto.HasField('fileID') and self._receipt_proto.fileID.fileNum != 0:
85+
return FileId._from_proto(self._receipt_proto.fileID)
86+
else:
87+
return None
88+
7889
@property
7990
def transaction_id(self):
8091
"""

0 commit comments

Comments
 (0)