Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions contract/src/jetton/JettonTransfer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package org.ton.contract.jetton

import org.ton.bigint.toBigInt
import org.ton.block.Coins
import org.ton.block.MsgAddressInt
import org.ton.cell.Cell
import org.ton.cell.CellBuilder
import org.ton.cell.CellSlice
import org.ton.tlb.TlbConstructor

public const val OP_JETTON_TRANSFER: Int = 0xf8a7ea5

public data class JettonTransfer(
val queryId: ULong,
val amount: Coins,
val toAddress: MsgAddressInt,
val responseAddress: MsgAddressInt,
val forwardAmount: Coins,
val forwardPayload: Cell?,
val customPayload: Cell?
) {
public companion object : TlbConstructor<JettonTransfer>(
"jetton_transfer query_id:uint64 amount:coins to_address:MsgAddress response_address:MsgAddress custom_payload:Maybe ^Cell forward_amount:coins forward_payload:Maybe ^Cell = JettonTransfer"
) {
override fun loadTlb(cellSlice: CellSlice): JettonTransfer {
val opCode = cellSlice.loadUInt(32)
require(opCode == OP_JETTON_TRANSFER.toBigInt()) { "Invalid op code" }

val queryId = cellSlice.loadULong()
val amount = Coins.loadTlb(cellSlice)
val toAddress = MsgAddressInt.loadTlb(cellSlice)
val responseAddress = MsgAddressInt.loadTlb(cellSlice)
val customPayload = cellSlice.loadNullableRef()
val forwardAmount = Coins.loadTlb(cellSlice)
val forwardPayload = cellSlice.loadNullableRef()

return JettonTransfer(
queryId = queryId,
amount = amount,
toAddress = toAddress,
responseAddress = responseAddress,
forwardAmount = forwardAmount,
forwardPayload = forwardPayload,
customPayload = customPayload
)
}

override fun storeTlb(cellBuilder: CellBuilder, value: JettonTransfer) {
cellBuilder.storeUInt(OP_JETTON_TRANSFER, 32)
cellBuilder.storeULong(value.queryId)
Coins.storeTlb(cellBuilder, value.amount)
MsgAddressInt.storeTlb(cellBuilder, value.toAddress)
MsgAddressInt.storeTlb(cellBuilder, value.responseAddress)
cellBuilder.storeNullableRef(value.customPayload)
Coins.storeTlb(cellBuilder, value.forwardAmount)
cellBuilder.storeNullableRef(value.forwardPayload)
}
}
}
35 changes: 35 additions & 0 deletions contract/test/jetton/JettonTransfer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.ton.contract.jetton

import kotlinx.io.bytestring.encodeToByteString
import org.ton.block.AddrStd
import org.ton.block.Coins
import org.ton.cell.CellBuilder
import org.ton.tlb.storeTlb
import kotlin.UInt
import kotlin.test.Test
import kotlin.test.assertEquals

class JettonTransferTest {
val transferJettonData = JettonTransfer(543u, Coins.ofNano(63546), AddrStd.parse("0QCLoGOnQ7fegGD5yLDk77QrH-I005hl_JlqMnXAyRyEJ8h0"), AddrStd.parse("0QBqtS9bao0LH0DxJYIPAGuwx8aRXuMmTxigj43E-Ef2Bl0o"),
Coins.ofNano(789), CellBuilder().storeUInt32(0u).storeByteString("Hello, this is a comment".encodeToByteString()).endCell(), null)

@Test
fun testJettonTransferDataEncodeAndDecode() {
val cell = CellBuilder().storeTlb(JettonTransfer, transferJettonData).endCell()

val decodedJttonTransferData = JettonTransfer.loadTlb(cell)

assertEquals(transferJettonData.queryId, decodedJttonTransferData.queryId)
assertEquals(transferJettonData.amount, decodedJttonTransferData.amount)
assertEquals(transferJettonData.toAddress, decodedJttonTransferData.toAddress)
assertEquals(transferJettonData.responseAddress, decodedJttonTransferData.responseAddress)
assertEquals(transferJettonData.forwardAmount, decodedJttonTransferData.forwardAmount)
assertEquals(transferJettonData.forwardPayload, decodedJttonTransferData.forwardPayload)
}

@Test
fun testJettonTransferTataEncode() {
val cell = CellBuilder().storeTlb(JettonTransfer, transferJettonData)
assertEquals(cell.toString(), "x{0F8A7EA5000000000000021F2F83A8011740C74E876FBD00C1F39161C9DF68563FC469A730CBF932D464EB819239084F001AAD4BD6DAA342C7D03C496083C01AEC31F1A457B8C993C62823E3713E11FD8184062B}\n x{0000000048656C6C6F2C2074686973206973206120636F6D6D656E74}")
}
}
83 changes: 83 additions & 0 deletions contract/test/jetton/JettonTransferExample.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package org.ton.contract.wallet

import io.github.andreypfau.kotlinx.crypto.sha2.sha256
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.ton.api.pk.PrivateKeyEd25519
import org.ton.block.AddrStd
import org.ton.block.Coins
import org.ton.block.MsgAddressInt
import org.ton.cell.CellBuilder
import org.ton.contract.jetton.JettonTransfer
import org.ton.kotlin.account.Account
import org.ton.kotlin.message.MessageLayout
import org.ton.tlb.storeTlb
import kotlin.test.Test

class JettonTransferExample {
@Test
fun walletV4Example(): Unit = runBlocking {
val liteClient = liteClientTestnet()

val pk = PrivateKeyEd25519(sha256("example-key".encodeToByteArray()))

val contract = WalletV4R2Contract(
liteClient,
WalletV4R2Contract.address(pk)
)
val testnetNonBounceAddr =
contract.address.toString(userFriendly = true, testOnly = true, bounceable = false)
println("Wallet Address: $testnetNonBounceAddr")

var accountState = liteClient.getAccountState(contract.address)
val account = accountState.account.value as? Account
if (account == null) {
println("Account $testnetNonBounceAddr not initialized")
return@runBlocking
}

val balance = account.storage.balance.coins
println("Account balance: $balance toncoins")

val toAddress = AddrStd("0QBFbLhcjyVqeLgYgNxroeXr5eaNXe4l4l3ekU4xLS57-cER");

val jettonData = JettonTransfer(
queryId = ULongRange(ULong.MIN_VALUE, ULong.MAX_VALUE).random(),
amount = Coins.ofNano(1),
toAddress = toAddress,
responseAddress = contract.address,
forwardAmount = Coins.ZERO,
forwardPayload = null,
customPayload = null
)

val transferDataCell = CellBuilder().storeTlb(JettonTransfer, jettonData).endCell()

contract.transfer(pk) {
destination = MsgAddressInt.parse("kQDdAW8SkFA9Zplv-v6ysn_n4TboKLMSohBi7iipZJ3flHff") // Jetton wallet address
coins = Coins.ofNano(40000000) // Fee
messageData = MessageData.Raw(transferDataCell, null, MessageLayout.PLAIN)
}

while (true) {
println("Wait for transaction to be processed...")
delay(6000)
val newAccountState = liteClient.getAccountState(contract.address)
if (newAccountState != accountState) {
accountState = newAccountState
println("Got new account state with last transaction: ${accountState.lastTransactionId}")
break
}
}

val lastTransactionId = accountState.lastTransactionId
if (lastTransactionId == null) {
println("No transactions found")
return@runBlocking
}

val transaction = liteClient.getTransactions(accountState.address, lastTransactionId, 1)
.first().transaction.value
println("Transaction: $lastTransactionId")
}
}