Working with Messages
The nekoton-python
library provides a robust set of tools to work with messages in the TVM blockchain. Messages are crucial for communication between smart contracts or between smart contracts and external applications. This guide will walk you through the different types of messages and how to work with them.
Message Types
In TVM blockchains, there are three types of messages:
- External Inbound Messages
- Internal Messages
- External Outbound Messages
These message types are represented by the MessageType
enum in nekoton-python
.
Initialization Methods
The Message
class offers two static methods that allow you to manually initialize a Message
object. You can either use raw bytes or a cell to create a new instance of a Message
.
Initialize from Raw Bytes
To create a Message
object from raw bytes, use the from_bytes
method. The bytes should contain the BOC (Bag-of-Cells) data.
message_raw_bytes = b"\xb5\xee\x9cr\x01\x01\x01\x01\x00\x81\x00\x00\xfd\x88\x00\r\x88\t3\x17iM\xeb\x9f\xc8\xcb'<|j\xc5\xda\xae|O\xef \xc6\xabcS\xc3\x9e\xc3\xebt\\\x05ag\x92\xed\xdc\xbe'(\x8a5\xbfI\xc5/\x18\x8a\xfa\x0b9y_+40\xd3\xe3\xbc\xf2\xd1\xe6W\xec\x98~\xca\xa61aB\x12\x12sS\x8e7\xed\xcd\\\xb2\xefX\x90\xbc\x9c\xf3 mk;\xa2\xbf\x84T\x14\x00\x00\x06'\xc2Fz\xf1\x00\x08\xbd\xc8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaa"
# Initialize the message from the raw bytes
message = Message.from_bytes(message_raw_bytes)
print(message)
Result:
<Message hash='dcbfbbce5aecdb9a486b8009e5f193f8bc7006455643e7ed83044afabe5cb8f2', ExternalIn>
Initialize from a Cell
To create a Message
object from a Cell
, use the from_cell
method. The cell should be the root message cell.
message_base64_boc = "te6ccgEBAQEAgQAA/YgADYgJMxdpTeufyMsnPHxqxdqufE/vIMarY1PDnsPrdFwFYWeS7dy+JyiKNb9JxS8YivoLOXlfKzQw0+O88tHmV+yYfsqmMWFCEhJzU4437c1csu9YkLyc8yBtazuiv4RUFAAABifCRnrxAAi9yAAAAAAAAAAAAAAAAAAAAKo="
# Decode the message cell
message_cell = nt.Cell.decode(message_base64_boc)
# Initialize the message from the cell
message = Message.from_cell(message_cell)
print(message)
Result:
<Message hash='dcbfbbce5aecdb9a486b8009e5f193f8bc7006455643e7ed83044afabe5cb8f2', ExternalIn>
Decoding Messages
The Message
class offers a static method for decoding a Message
object from an encoded BOC string. The method allows you to specify the encoding type, which can be either base64
(default) or hex
.
From an Encoded BOC String
# Decode the message from an encoded BOC string
message = Message.decode(encoded_boc_string, encoding='base64')
print(message)
Result:
<Message hash='dcbfbbce5aecdb9a486b8009e5f193f8bc7006455643e7ed83044afabe5cb8f2', ExternalIn>
TIP
This method is particularly useful when the encoded BOC string is obtained from an external source, such as a blockchain explorer. It is more appropriate to use this method in such cases, rather than when you have created a cell during the initialization step.
Encoding Messages
The Message
class provides methods for encoding a Message
object into different formats: a string, bytes, or a cell.
To a String
To encode a Message
object into a string, use the encode
method.
# Encode the message to a string
encoded_message = message.encode('base64')
print(encoded_message)
Result:
te6ccgEBAQEAgQAA/YgADYgJMxdpTeufyMsnPHxqxdqufE/vIMarY1PDnsPrdFwFYWeS7dy+JyiKNb9JxS8YivoLOXlfKzQw0+O88tHmV+yYfsqmMWFCEhJzU4437c1csu9YkLyc8yBtazuiv4RUFAAABifCRnrxAAi9yAAAAAAAAAAAAAAAAAAAAKo=
To Bytes
To encode a Message
object into bytes, use the to_bytes
method.
# Encode the message to bytes
encoded_message_bytes = message.to_bytes()
print(encoded_message_bytes)
Result:
b"\xb5\xee\x9cr\x01\x01\x01\x01\x00\x81\x00\x00\xfd\x88\x00\r\x88\t3\x17iM\xeb\x9f\xc8\xcb'<|j\xc5\xda\xae|O\xef \xc6\xabcS\xc3\x9e\xc3\xebt\\\x05ag\x92\xed\xdc\xbe'(\x8a5\xbfI\xc5/\x18\x8a\xfa\x0b9y_+40\xd3\xe3\xbc\xf2\xd1\xe6W\xec\x98~\xca\xa61aB\x12\x12sS\x8e7\xed\xcd\\\xb2\xefX\x90\xbc\x9c\xf3 mk;\xa2\xbf\x84T\x14\x00\x00\x06'\xc2Fz\xf1\x00\x08\xbd\xc8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaa"
Build a Cell from a Message
To build a cell from a Message
object, use the build_cell
method.
# Build a cell from the message
cell = message.build_cell()
print(cell)
Result:
<Cell repr_hash='dcbfbbce5aecdb9a486b8009e5f193f8bc7006455643e7ed83044afabe5cb8f2', bits=1014, refs=0>
Checking Types
You can check the type of a message using the following properties:
# Check if a message is an external inbound message
is_external_in = message.is_external_in()
# Check if a message is an external outbound message
is_external_out = message.is_external_out()
# Check if a message is an internal message
is_internal = message.is_internal()
print(is_external_in)
print(is_external_out)
print(is_internal)
Result:
True
False
False
Message Headers
Message headers contain essential information about the message, such as the source and destination addresses, the value attached to the message, and more.
Accessing Headers
You can access the header of a message using the header
property:
# Get the header of a message
header = message.header
# Get the type of the message from the header
message_type = header.type
print(header)
print(message_type)
Result:
<builtins.ExternalInMessageHeader object at 0x101c224f0>
ExternalIn
Header Properties
Depending on the type of the message, you can access more information from the header.
Internal Messages
For internal messages, you can access the following properties:
ihr_disabled = header.ihr_disabled
bounce = header.bounce
bounced = header.bounced
src = header.src
dst = header.dst
value = header.value
ihr_fee = header.ihr_fee
fwd_fee = header.fwd_fee
created_at = header.created_at
created_lt = header.created_lt
Result:
True
True
False
0:3c9893fce0c401e7890eec6599c0b79be25314299ec47a9e78627c42e947d792
0:06c404998bb4a6f5cfe465939e3e3562ed573e27f7906355b1a9e1cf61f5ba2e
1
0
0.000333336
1694222262
21376022000002
External Inbound Messages
For external inbound messages, you can access the following properties:
dst = header.dst
import_fee = header.import_fee
print(dst)
print(import_fee)
Result:
0:06c404998bb4a6f5cfe465939e3e3562ed573e27f7906355b1a9e1cf61f5ba2e
0
External Outbound Messages
For external outbound messages, you can access the following properties:
src = header.src
created_at = header.created_at
created_lt = header.created_lt
print(src)
print(created_at)
print(created_lt)
Result:
0:06c404998bb4a6f5cfe465939e3e3562ed573e27f7906355b1a9e1cf61f5ba2e
1694222262
21376022000004
Unsigned External Message
An UnsignedExternalMessage
represents an external message before it's signed. It provides a structured way to manage and manipulate messages before they are signed and sent to smart contracts.
Properties of an Unsigned External Message
Hash
The hash
property returns the hash of the message.
# Get the hash of the unsigned message
hash = unsigned_message.hash
print(hash)
Result:
b'{\x9f\xf0\xab8\xd4G\xf7\xc9\xedTV\x97//\xb4\x05o\xc0\x9eS\xed-\x81\xb24\x86I\xf0\xe3\x10\xf1'
Expiration Time
The expire_at
property returns the expiration unix timestamp of the message.
# Get the expiration time of the unsigned message
expire_at = unsigned_message.expire_at
print(expire_at) # 1695429486
Signing an Unsigned External Message
The sign
method allows you to sign an unsigned external message. This requires a KeyPair
and an optional signature_id
.
# Sign the unsigned message
signed_message = unsigned_message.sign(keypair, signature_id)
print(signed_message)
Result:
<SignedExternalMessage hash='6bddfe073564fe7439f3132c21be0d436c4a98638d8d28e35052237b1393e1d1', expire_at=1695429300, ExternalIn>
Inserting a Signature
The with_signature
method allows you to insert a signature into the body of the message. This requires a valid ed25519
signature as the parameter.
# Create a signature
signature = keypair.sign_raw(unsigned_message.hash)
# Insert a signature into the body of the message
signed_message = unsigned_message.with_signature(signature)
print(signed_message)
Result:
<SignedExternalMessage hash='6bddfe073564fe7439f3132c21be0d436c4a98638d8d28e35052237b1393e1d1', expire_at=1695429300, ExternalIn>
Inserting a Fake Signature
The with_fake_signature
method allows you to insert a fake signature into the body of the message. This method does not require any parameters.
# Insert a fake signature into the body of the message
signed_message = unsigned_message.with_fake_signature()
print(signed_message)
Result:
<SignedExternalMessage hash='b8996a7f99e77e5c3dac9bfdf109d5c61dc83dde17fbc97809903c6f2102335b', expire_at=1695429300, ExternalIn>
TIP
This method is useful for testing purposes when you need to create a signed message but the validity of the signature is not important.
Creating a Message Without a Signature
The without_signature
method allows you to create a message without a signature. This method does not require any parameters.
# Create a message without a signature
signed_message = unsigned_message.without_signature()
print(without_signature_message)
Result:
<SignedExternalMessage hash='46c8f90a137f5141a2d8c8157878d36cec4f47f281f4fa2865c856738674fa6c', expire_at=1695429300, ExternalIn>
Unsigned Message Body
Signing the External Message Body
The sign
method of the UnsignedBody
class allows you to sign the body of an external message. This requires a KeyPair
and an optional signature_id
.
# Sign the body of the unsigned message
signed_body = unsigned_body.sign(keypair, signature_id)
print(signed_body)
Result:
<Cell repr_hash='d35449d816f78e69cae9a447fa00097468a84272d1a4480d92cd7e071355318b', bits=737, refs=0>
Inserting a Signature
The with_signature
method allows you to insert a signature into the body of the message. This requires a valid ed25519
signature as the parameter.
# Insert a signature into the body of the message
signed_body = unsigned_body.with_signature(signature)
print(signed_body)
Result:
<Cell repr_hash='d35449d816f78e69cae9a447fa00097468a84272d1a4480d92cd7e071355318b', bits=737, refs=0>
Inserting a Fake Signature
The with_fake_signature
method allows you to insert a fake signature into the body of the message. This method does not require any parameters.
# Insert a fake signature into the body of the message
signed_body = unsigned_body.with_fake_signature()
print(signed_body)
Result:
<Cell repr_hash='cd620bb427b5a0662cffccad9015558e5ed236226bc389f6a3b1183a738ad820', bits=737, refs=0>
TIP
This method is useful for testing purposes when you need to create a signed message body but the validity of the signature is not important.
Creating a Message Body Without a Signature
The without_signature
method allows you to create a message body without a signature. This method does not require any parameters.
# Create a message body without a signature
signed_body = unsigned_body.without_signature()
print(signed_body)
Result:
<Cell repr_hash='fdf652f24c10b6b8e765c6f6bfac82681190f17274ff492647dcd4be76e7eb1a', bits=225, refs=0>
Internal Message Body
While the UnsignedBody
class is used for external message bodies, an internal message body is represented as a Cell
. A Cell
can be passed into an external message body.
This is useful in the following situations:
To create an internal message for a local executor. This is helpful when you need to evaluate a specific part of a chain in isolation or for testing purposes.
For contracts that simply forward the received
Cell
with the message. For example, in EverWallet, there is a separate method that can send up to four messages in this way.
You can create, manipulate, and evaluate Cell
instances using the methods provided in the client libraries. However, unlike UnsignedBody
, a Cell
doesn't need to be signed or carry a signature because it's meant for internal use and not for external validation.
TIP
Please consult ourguide on Working With Cells for detailed instructions on how to manipulate Cell
instances for internal message bodies
Signed External Messages
A SignedExternalMessage
represents an external message after it's signed.
Properties of a Signed External Message
Hash
The hash
property returns the hash of the message.
# Get the hash of the signed message
hash = signed_message.hash
print(hash)
Result:
b"\xc7\xc3\xa4 @\x0f\xdf\xc6#\xd8\x1c\\\xdd\x0e\xd7\x0f\x01\x9f\x18M\xe53\xa8\x94\xcc\x8f$\x10'\xd5\x95\xd5"
Expiration Time
The expire_at
property returns the expiration unix timestamp of the message.
# Get the expiration time of the signed message
expire_at = signed_message.expire_at
print(expire_at) # 1695429300
Splitting a Signed Message
The split
method allows split into inner message and expiration timestamp.
# Split the signed message
inner_message, expire_at = signed_message.split()
print(inner_message)
print(expire_at)
Result:
<Message hash='2db0a84737948e6ea62108e9a152ee08e00adfed8c2504fadfc530d54918f40f', ExternalIn>
1695429300