Sending messages
TON Connect has more powerful options than just authenticating users in the dApp; it also allows sending outgoing messages via connected wallets.
You will understand:
- how to send messages from the DApp to the blockchain
- how to send multiple messages in one transaction
- how to deploy a contract using TON Connect
Playground page
We will use the low level TON Connect SDK for JavaScript. We'll experiment in the browser console on a page where the wallet is already connected. Here is the sample page:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://unpkg.com/@tonconnect/sdk@latest/dist/tonconnect-sdk.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/tonweb.js"></script>
</head>
<body>
<script>
window.onload = async () => {
window.connector = new TonConnectSDK.TonConnect({
manifestUrl: 'https://ratingers.pythonanywhere.com/ratelance/tonconnect-manifest.json'
});
connector.restoreConnection();
}
</script>
</body>
</html>
Feel free to copy-paste it into your browser console and run it.
Sending multiple messages
Understanding a task
We will send two messages in one transaction: one to your address, carrying 0.2 TON, and one to the other wallet address, carrying 0.1 TON.
By the way, there is a limit to the number of messages sent in one transaction:
- standard (v3/v4) wallets: 4 outgoing messages;
- highload wallets: 255 outgoing messages (close to blockchain limitations).
Sending the messages
Run the following code:
console.log(await connector.sendTransaction({
validUntil: Math.floor(new Date() / 1000) + 360,
messages: [
{
address: connector.wallet.account.address,
amount: "200000000"
},
{
address: "EQCyoez1VF4HbNNq5Rbqfr3zKuoAjKorhK-YZr7LIIiVrSD7",
amount: "100000000"
}
]
}));
You'll notice that this command does not print anything into the console, null
or undefined
, as functions returning nothing do. This means that connector.sendTransaction
does not exit immediately.
Open your wallet application, and you'll see why. There is a request showing what you are sending and where the coins would go. Please, accept it.
Getting the result
The function will exit, and the output from the blockchain will be printed:
{
boc: "te6cckEBAwEA4QAC44gBZUPZ6qi8Dtmm1cot1P175lXUARlUVwlfMM19lkERK1oCUB3RqDxAFnPpeo191X/jiimn9Bwnq3zwcU/MMjHRNN5sC5tyymBV3SJ1rjyyscAjrDDFAIV/iE+WBySEPP9wCU1NGLsfcvVgAAACSAAYHAECAGhCAFlQ9nqqLwO2abVyi3U/XvmVdQBGVRXCV8wzX2WQRErWoAmJaAAAAAAAAAAAAAAAAAAAAGZCAFlQ9nqqLwO2abVyi3U/XvmVdQBGVRXCV8wzX2WQRErWnMS0AAAAAAAAAAAAAAAAAAADkk4U"
}
BoC is bag of cells, the way data is stored in TON. Now, we can decode it.
Decode this BoC in the tool of your choice, and you'll get the following tree of cells:
x{88016543D9EAA8BC0ED9A6D5CA2DD4FD7BE655D401195457095F30CD7D9641112B5A02501DD1A83C401673E97A8D7DD57FE38A29A7F41C27AB7CF0714FCC3231D134DE6C0B9B72CA6055DD2275AE3CB2B1C023AC30C500857F884F960724843CFF70094D4D18BB1F72F5600000024800181C_}
x{42005950F67AAA2F03B669B5728B753F5EF9957500465515C257CC335F6590444AD6A00989680000000000000000000000000000}
x{42005950F67AAA2F03B669B5728B753F5EF9957500465515C257CC335F6590444AD69CC4B40000000000000000000000000000}
This is a serialized external message, and two references are outgoing messages representations.
x{88016543D9EAA8BC0ED9A6D5CA2DD4FD7BE655D401195457095F30CD7D964111...
$10 ext_in_msg_info
$00 src:MsgAddressExt (null address)
"EQ..."a dest:MsgAddressInt (your wallet)
0 import_fee:Grams
$0 (no state_init)
$0 (body starts in this cell)
...
Returning the BoC of the sent transaction is to track it.
Processing transactions initiated with TON Connect
To find a transaction by extInMsg
, you need to do the following:
- Parse the received
extInMsg
as a cell. - Calculate the
hash()
of the obtained cell.
The received hash is what the sendBocReturnHash
methods of TON Center API are already returning to you.
-
Search for the required transaction using this hash through an indexer:
-
Using TON Center api_v3_transactionsByMessage_get.
-
Using the
/v2/blockchain/messages/{msg_id}/transaction
method from TON API. -
Collect transactions independently and search for the required extInMsg by its hash: see example.
-
It's important to note that extInMsg
may not be unique, which means collisions can occur. However, all transactions are unique.
If you are using this for an informative display, this method should be sufficient. With standard wallet contracts, collisions can occur only in exceptional situations.
Sending complex transactions
Serialization of cells
Before we proceed, let's talk about the format of the messages we will send.
- payload (string base64, optional): raw one-cell BoC encoded in Base64.
- We will use it to store text comments on transfer
- stateInit (string base64, optional): raw one-cell BoC encoded in Base64.
- We will use it to deploy a smart contract
After building a message, you can serialize it into BoC.
TonWeb.utils.bytesToBase64(await payloadCell.toBoc())
Transfer with comment
You can use toncenter/tonweb JS SDK or your favourite tool to serialize cells to BoC.
Text comments on transfer are encoded as opcode 0 (32 zero bits) + UTF-8 bytes of comment. Here's an example of how to convert it into a bag of cells.
let a = new TonWeb.boc.Cell();
a.bits.writeUint(0, 32);
a.bits.writeString("TON Connect tutorial!");
let payload = TonWeb.utils.bytesToBase64(await a.toBoc());
console.log(payload);
// te6ccsEBAQEAHQAAADYAAAAAVE9OIENvbm5lY3QgMiB0dXRvcmlhbCFdy+mw
Smart contract deployment
And we'll deploy an instance of super simple chatbot Doge, mentioned as one of smart contract examples. First of all, we load its code and store something unique in data to receive our very own instance that someone else has not deployed. Then, we combine code and data into stateInit.
let code = TonWeb.boc.Cell.oneFromBoc(TonWeb.utils.base64ToBytes('te6cckEBAgEARAABFP8A9KQT9LzyyAsBAGrTMAGCCGlJILmRMODQ0wMx+kAwi0ZG9nZYcCCAGMjLBVAEzxaARfoCE8tqEssfAc8WyXP7AN4uuM8='));
let data = new TonWeb.boc.Cell();
data.bits.writeUint(Math.floor(new Date()), 64);
let state_init = new TonWeb.boc.Cell();
state_init.bits.writeUint(6, 5);
state_init.refs.push(code);
state_init.refs.push(data);
let state_init_boc = TonWeb.utils.bytesToBase64(await state_init.toBoc());
console.log(state_init_boc);
// te6ccsEBBAEAUwAABRJJAgE0AQMBFP8A9KQT9LzyyAsCAGrTMAGCCGlJILmRMODQ0wMx+kAwi0ZG9nZYcCCAGMjLBVAEzxaARfoCE8tqEssfAc8WyXP7AAAQAAABhltsPJ+MirEd
let doge_address = '0:' + TonWeb.utils.bytesToHex(await state_init.hash());
console.log(doge_address);
// 0:1c7c35ed634e8fa796e02bbbe8a2605df0e2ab59d7ccb24ca42b1d5205c735ca
And it's time to send our transaction:
console.log(await connector.sendTransaction({
validUntil: Math.floor(new Date() / 1000) + 360,
messages: [
{
address: "EQAcfDXtY06Pp5bgK7voomBd8OKrWdfMskykKx1SBcc1yh5O",
amount: "69000000",
payload: "te6ccsEBAQEAHQAAADYAAAAAVE9OIENvbm5lY3QgMiB0dXRvcmlhbCFdy+mw",
stateInit: "te6ccsEBBAEAUwAABRJJAgE0AQMBFP8A9KQT9LzyyAsCAGrTMAGCCGlJILmRMODQ0wMx+kAwi0ZG9nZYcCCAGMjLBVAEzxaARfoCE8tqEssfAc8WyXP7AAAQAAABhltsPJ+MirEd"
}
]
}));
Get more examples on the Preparing Messages page for Transfer NFT and Jettons.
After confirmation, we may see our transaction complete at tonscan.org.
What happens if the user rejects a transaction request?
It's pretty easy to handle request rejection, but it's better to know what would happen in advance when you're developing some project.
When a user clicks Cancel in the popup in the wallet application, an exception is thrown:
Error: [TON_CONNECT_SDK_ERROR] The Wallet declined the request
This error can be considered final (unlike connection cancellation) - if it has been raised, then the requested transaction will definitely not happen until the next request is sent.