DQtech Column | Learn from DEX about PlatON Application Development – Android (II)

Author Dex_DQT

In the previous chapter, we finished setting up the environment and calling the SDK to get the balance of the specified wallet, but the calls provided by the SDK have to be used in a sub-thread, which is troublesome. Therefore, in this chapter, we would encapsulate the interface through JSONRPC, which is easier to be used.

Introduce the retrofit package

Open app/build.gradle, as follow

1641032493(1)
Add dependency injections shown in follows:

dependencies {
    implementation 'com.google.android.material:material:1.4.0'

    implementation 'com.platon.client:core:0.13.0.2-android'
    implementation 'com.platon.client:crypto:0.13.0.2-android'

    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-jackson:2.9.0'
}

modify PlatonApi as follows:

This project uses retrofit and koltin’s concurrency to implement http calls.

interface IPlatonApi {
    @POST("/")
    suspend fun Call(@retrofit2.http.Body params: PlatonParams): PlatonResult
}
class PlatonApi {
    companion object {
        /**
         * 测试网地址
         */
        const val url: String = "http://35.247.155.162:6789"

        /**
         * 创建retrofit的httpClient对象
         */
        fun <T> Create(tClass: Class<T>): T {

            val okHttpClient = OkHttpClient.Builder().build()
            val retrofit: Retrofit = Retrofit.Builder().baseUrl(url)
                .addConverterFactory(JacksonConverterFactory.create())
                .client(okHttpClient)
                .build()
            return retrofit.create(tClass)
        }

        init {
            /**
             *  初始化钱包应用
             */
            WalletApplication.init(
                WalletApplication.MAINNET,
                AddressManager.ADDRESS_TYPE_BECH32,
                AddressBehavior.CHANNLE_PLATON
            )
        }

        /**
         * 创建httpClient实例
         */
        private fun getHttpApi(): IPlatonApi {
            return Create(IPlatonApi::class.java)
        }

        /**
         * 获取钱包余额
         */
        suspend fun GetBalance(walletAddress: String): Double {
            val params = ArrayList<Any>()
            with(params) {
                add(walletAddress)
                add("latest")
            }
            val platonResult = doCall("platon_getBalance", params)
            val lat = Numeric.decodeQuantity(platonResult.result)
            return Convert.fromVon(BigDecimal(lat), Convert.Unit.LAT).toDouble()
        }
}

It should be noticed that ,be sure to initialize the wallet application when the application starts, i.e. the above code init block, which implements its own interface to get the balance. The cold is as follows:

    private fun initEvent() {
        binding.btn.setOnClickListener {
            lifecycleScope.launch {
                /**
                 * 获取钱包余额
                 */
                val lat = PlatonApi.GetBalance("lat1tgu6pts6nhmneu5zhqly3rc83r6y6ecfmde03e")
                val asdasd = 10;
            }
        }
    }

The wallet adress used here is the same of that in JavaScript(II):
The balance is 184.81058LAT
1641033018(1)

The balance shouwn in browser is as follow:

1641033060(1)

You can see that the interface we wrote gets the same balance as the browser. Obviously, by using our own encapsulated interface, it is easier and simpler to use than the one in Chapter 1.

Get Address Hrp

Add code bellow in PlatonApi:

        /**
         * 获取HRP
         */
        suspend fun GetAddressHrp(): String? {
            val params = ArrayList<Any>()
            val platonResult = doCall("platon_getAddressHrp", params)
            return platonResult.result
        }

The call is made as follows:

val hrp = PlatonApi.GetAddressHrp();

The result:

1641033281(1)

Get Gas Price

Add code in PlatonApi:

        /**
         * 获取当前gas价格
         */
        suspend fun GetGasPrice(): BigInteger {

            val params = ArrayList<Any>()
            val platonResult = doCall("platon_gasPrice", params)
            return Numeric.decodeQuantity(platonResult.result)
        }

Because the call is basically the same, I will not keep writing it out, we can directly look at the results. gasPrice is shown below:

1641033415(1)

Get Block By Number

We need a custom class to receive block information. Through the official documentation, we can define the block information class as follows:

@JsonIgnoreProperties(ignoreUnknown = true)
class PlatonBlockInfo {
    /**
     *  块编号
     */
    var number: String = ""
        set(value) {
            field = Numeric.decodeQuantity(value).toString()
        }

    /**
     * 块哈希,处于 pending 状态的块为 null
     */
    var hash: String = ""

    /**
     * 父块哈希
     */
    var parentHash: String = ""

    /**
     * 生成的 proof-of-work 的哈希,处于 pending 状态的块为 null
     */
    var nonce: String = ""

    /**
     * 块中日志的 bloom filter,处于 pending 状态的块为 null
     */
    var logsBloom: String = ""

    /**
     * 块中的交易树根节点
     */
    var transactionsRoot: String = ""

    /**
     * 块中的最终状态树根节点
     */
    var stateRoot: String = ""

    /**
     *  接收奖励的矿工地址
     */
    var miner: String = ""

    /**
     * 块 “extra data” 字段
     */
    var extraData: String = ""

    /**
     * 字节为单位的块大小
     */
    var size: String = ""
        set(value) {
            field = Numeric.decodeQuantity(value).toString()
        }

    /**
     *  该块允许的最大 gas 值
     */
    var gasLimit: String = ""
        set(value) {
            field = Numeric.decodeQuantity(value).toString()
        }

    /**
     * 该块中所有交易使用的 gas 总量
     */
    var gasUsed: String = ""
        set(value) {
            field = Numeric.decodeQuantity(value).toString()
        }

    /**
     * 出块的 unix 时间戳
     */
    var timestamp: String = ""
        set(value) {
            field = Numeric.decodeQuantity(value).toString()
        }
    var transactions: List<Any> = ArrayList()
    
}

Because this interface returns more fields, this class does not accept all fields, so you need to add the annotation @JsonIgnoreProperties in the class to ignore the extra fields, if not added, it will prompt an exception. Notice: The return fields of size, gasLimit,gasUsed,timestamp hexadecimal, which need to be manually converted to decimal values for readability.
Next, add the interface Call2 to the interface IPlatonApi specifically for receiving block messages. The code is as follows:

interface IPlatonApi {

    @POST("/")
    suspend fun Call(@retrofit2.http.Body params: PlatonParams): PlatonResult

    @POST("/")
    suspend fun Call2(@retrofit2.http.Body params: PlatonParams): PlatonBlockResult
}

Next, we add the doCall2 function to PlatonApi to call the Call2 interface

...省略代码
        suspend fun doCall2(method: String, params: ArrayList<Any>): PlatonBlockResult {

            val platonParams = PlatonParams(
                method = method,
                params = params,
                id = 1
            )
            val httpApi = getHttpApi()
            return httpApi.Call2(platonParams)
        }
...省略代码

Finally, add the implementation code of GetBlockByNumber in PlatonApi class as follows:

        /**
         * 获取指定块的信息
         */
        suspend fun GetBlockByNumber(blockNumber: Long): PlatonBlockInfo {
            val params = ArrayList<Any>()
            val blockNumberBN = BigInteger(blockNumber.toString())
            with(params) {
                add(Numeric.encodeQuantity(blockNumberBN))
                add(false)
            }
            return doCall2("platon_getBlockByNumber", params).result
        }

We get the same block as in Chapter 2 of the JavaScript chapter. The block number is 6533257, and the result is as follows:

1641034051(1)


The results are the same as in the second chapter of JavaScript.

Get Block Transaction Count By Number

Add the code to Get Block Transaction Count By Number in PlatonApi:

        suspend fun GetBlockTransactionCountByNumber(blockNumber: Long): Long {
            val params = ArrayList<Any>()
            val blockNumberBN = BigInteger(blockNumber.toString())
            with(params) {
                add(Numeric.encodeQuantity(blockNumberBN))
            }
            val platonResult = doCall("platon_getBlockTransactionCountByNumber", params)
            return Numeric.decodeQuantity(platonResult.result).toLong()
        }

Since we already know that this block is an empty block, we can see that the number of transactions is 0
1641034273(1)

Get Block Transaction Count By Hash

Add the code to GetBlockTransactionCountByHash in PlatonApi:

        /**
         * 通过块的哈希值获取块的交易数量
         */
        suspend fun GetBlockTransactionCountByHash(blockHash: String): Long {
            val params = ArrayList<Any>()
            params.add(blockHash)
            val platonResult = doCall("platon_getBlockTransactionCountByHash", params)
            return Numeric.decodeQuantity(platonResult.result).toLong()
        }

Through the GetBlockByNumber interface we know that the hash value of this block is 0x0561ab627d3053c486a552e594f6b3f40f7acc2fd107866169feb34de346129b, this interface gets the number of transactions of this block is also 0, as shown below:

1641034501(1)

Get Transaction Count

Add the implementation code of GetTransactionCount in PlatonApi class, as follows:

        /**
         * 获取交易数量
         */
        suspend fun GetTransactionCount(walletAddress: String): BigInteger {
            val params = ArrayList<Any>()

            with(params) {
                add(walletAddress)
                add("latest")
            }
            val platonResult = doCall("platon_getTransactionCount", params)

            return Numeric.decodeQuantity(platonResult.result)
        }

Here we get the number of transactions for the wallet address lat1tgu6pts6nhmneu5zhqly3rc83r6y6ecfmde03e,

and the result is shown below:

1641034653(1)

There is 38 transactions shown on the browser.

1641034723(1)

In the JavaScript article we already know that the accepted records are not counted in the number of transactions, and this account has 18 received records, so the number of the total record is 38-18=20, which is the same as the value obtained from our interface.

Complete the first transfer

The conversion has to get the transaction receipt through the transaction hash, and the information of the transaction receipt is as follows:

@JsonIgnoreProperties(ignoreUnknown = true)
class PlatonReceiptInfo {
    /**
     * 区块的哈希值。 等待被打包时为null 。
     */
    var blockHash: String = ""

    /**
     * 20字节 - 交易的发送地址。
     */
    var from: String = ""

    /**
     * 32字节 - 创建此日志的事务的哈希值。当其挂起的日志时为null。
     */
    var transactionHash: String = ""

    /**
     *  区块中交易索引的位置,未打包时为null。
     */
    var transactionIndex: String = ""
        set(value) {
            field = Numeric.decodeQuantity(value).toString()
        }

    /**
     * 该交易所在的区块号。null,待处理。
     */
    var blockNumber: String = ""
        set(value) {
            field = Numeric.decodeQuantity(value).toString()
        }

    /**
     *  在区块中执行此交易时使用的天然气总量。

     */
    var cumulativeGasUsed: String = ""
        set(value) {
            field = Numeric.decodeQuantity(value).toString()
        }

    /**
     *
     */
    var contractAddress: String? = null

    /**
     * 成功状态 成功为1 失败为0
     */
    var status: String = ""
        set(value) {
            field = Numeric.decodeQuantity(value).toString()
        }

    /**
     *  仅此特定交易使用的天然气量。
     */
    var gasUsed: String = ""
        set(value) {
            field = Numeric.decodeQuantity(value).toString()
        }

    /**
     * 此事务生成的日志对象数组。
     */
    var logs: List<Any> = ArrayList();

}

Add Call3 to IPlatonApi to get the receipt of the transaction, the code is as follows

interface IPlatonApi {

    @POST("/")
    suspend fun Call(@retrofit2.http.Body params: PlatonParams): PlatonResult

    @POST("/")
    suspend fun Call2(@retrofit2.http.Body params: PlatonParams): PlatonBlockResult


    @POST("/")
    suspend fun Call3(@retrofit2.http.Body params: PlatonParams): PlatonReceiptResult
}

Add doCall3 to the PlatonApi class to get the transaction receipt with the following code:

        suspend fun doCall3(method: String, params: ArrayList<Any>): PlatonReceiptResult {

            val platonParams = PlatonParams(
                method = method,
                params = params,
                id = 1
            )
            val httpApi = getHttpApi()
            return httpApi.Call3(platonParams)
        }

Next we add the SendLATTO code for the interface to send LAT:

        /**
         * 发送LAT到制定者账号, 返回交易哈希
         */
        suspend fun SendLATTO(
            privateKey: String,
            toAddress: String,
            lat: Long
        ): PlatonReceiptInfo? {

            /**
             * 转换秘钥
             */
            val iPrivateKey =
                Numeric.toBigInt(privateKey)

            val keyPair = ECKeyPair.create(iPrivateKey)

            /**
             * 将秘钥转换成sdk使用的凭证类
             */
            val credentials: Credentials = Credentials.create(keyPair)

            /**
             * 设置gaslimit
             */
            val gasLimit = BigInteger.valueOf(21000)

            /**
             * 获取当前的gasprice
             */
            val gasPrice = GetGasPrice()

            /**
             * 获取交易数量用作nonce字段
             */
            val nonce: BigInteger = GetTransactionCount(credentials.address)

            /**
             * 将lat转换为von
             */
            val value = Convert.toVon(BigDecimal(lat), Convert.Unit.LAT).toBigInteger();
            /**
             * 构建交易类
             */
            val rawTransaction = RawTransaction.createTransaction(
                nonce,
                gasPrice,
                gasLimit,
                toAddress,
                value,
                ""
            )

            /**
             * 使用秘钥对交易数据进行签名
             */
            val signedMessage =
                TransactionEncoder.signMessage(rawTransaction, 210309, credentials)

            /**
             * 将交易数据转换为16进制
             */
            val hexValue = Numeric.toHexString(signedMessage)

            val params = ArrayList<Any>()

            params.add(hexValue)
            /**
             * 发送给节点,返回为交易哈希
             */
            val platonResult = doCall("platon_sendRawTransaction", params)

            /**
             * 根据交易哈希获取交易收据
             */
            return GetTransactionReceipt(platonResult.result)
        }

The interface to get the receipt through the transaction hash, the code is as follows:

        /**
         * 获取交易收据数据
         */
        suspend fun GetTransactionReceipt(txHash: String): PlatonReceiptInfo? {
            val params = ArrayList<Any>()

            with(params) {
                add(txHash)
            }

            return doCall3("platon_getTransactionReceipt", params).result
        }

Here we use the secret key of Javascript Chapter 2, a4ac816da1ab40f805d026009247002f47c8c0a9af95b35ca9741c576466e1a8, which corresponds to the wallet address of : lat1tgu6pts6nhmneu5zhqly3rc83r6y6ecfmde03e. We already know that the balance of this wallet is 184.81058 LAT.
Now we transfer 10 LAT to lat1zrq89dhle45g78mm4j8aq3dq5m2shpu56ggc6e, and get the receipt as shown below:

1641035619(1)

You can see from the status that the transfer was successful. The balance of the wallet in the browser is:

image

The funds were transferred successfully.

Well, that’s all of this chapter, in the next chapter we’ll start building Digging pages.

The URL of github: https://github.com/DQTechnology/Platon_DevGuideProject

This article is reproduced from https://forum.latticex.foundation/t/topic/5952

Like (0)
Previous February 15, 2022 10:50
Next February 15, 2022 14:59

相关推荐

Leave a Reply

Please Login to Comment