2

I am creating a simple Android application that is interfaced with a Counter program deployed on the Devnet Cluster.

Here is the Counter program

use anchor_lang::prelude::*;

declare_id!("AR14fj2ohwVkbZuAWhiox87EXGvB8Q1V1LukSXCqQWTN");

pub const ANCHOR_DISCRIMINATOR_SIZE: usize = 8;

#[program]
pub mod counter_dapp {
    use super::*;

    /// Initialises a Counter account
    pub fn initialise_counter(
        ctx: Context<InitialiseCounter>
    ) -> Result<()> {
        let counter = &mut ctx.accounts.counter;

        counter.count = 0;

        Ok(())
    }

    /// Increment the Counter.count value
    ///
    /// # Arguments
    /// * `amount` - An unsigned integer to increment the counter value by
    pub fn increment_counter(
        ctx: Context<IncrementCounter>,
        amount: u64
    ) -> Result<()> {
        let counter = &mut ctx.accounts.counter;

        counter.count += amount;

        Ok(())
    }
}

#[derive(Accounts)]
pub struct IncrementCounter<'info> {
    #[account(
        mut,
        seeds = [b"counter"],
        bump
    )]
    pub counter: Account<'info, Counter>,

    pub signer: Signer<'info>
}

#[derive(Accounts)]
pub struct InitialiseCounter<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,

    #[account(
        init,
        payer = signer,
        space = ANCHOR_DISCRIMINATOR_SIZE + Counter::INIT_SPACE,
        seeds = [b"counter"],
        bump
    )]
    pub counter: Account<'info, Counter>,

    pub system_program: Program<'info, System>
}

#[account]
#[derive(InitSpace)]
pub struct Counter {
    pub count: u64
}

Here is the Android Kotlin code within the ViewModel.

import com.funkatronics.kborsh.Borsh
import com.hansel.counterdapp.counter.domain.model.ArgsIncrementCounter
import com.hansel.counterdapp.counter.domain.model.Counter
import com.portto.solana.web3.Connection
import com.portto.solana.web3.PublicKey
import com.portto.solana.web3.rpc.types.config.Commitment
import com.portto.solana.web3.util.Cluster
import com.solana.mobilewalletadapter.clientlib.ActivityResultSender
import com.solana.mobilewalletadapter.clientlib.MobileWalletAdapter
import com.solana.mobilewalletadapter.clientlib.RpcCluster
import com.solana.mobilewalletadapter.clientlib.TransactionResult
import com.solana.mobilewalletadapter.clientlib.protocol.MobileWalletAdapterClient
import com.solana.mobilewalletadapter.clientlib.successPayload
import com.solana.programs.SystemProgram
import com.solana.publickey.ProgramDerivedAddress
import com.solana.publickey.SolanaPublicKey
import com.solana.serialization.AnchorInstructionSerializer
import com.solana.transaction.AccountMeta
import com.solana.transaction.Message
import com.solana.transaction.Transaction
import com.solana.transaction.TransactionInstruction
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.serialization.decodeFromByteArray
import org.bitcoinj.core.Base58
import javax.inject.Inject

fun initialiseCounter(
        activityResultSender: ActivityResultSender
    ) = viewModelScope.launch(Dispatchers.IO) {
        try {
            if (_counterState.value.authToken.isEmpty()) connectToWallet(activityResultSender)

            val userWalletAddress = SolanaPublicKey.from(_counterState.value.userAddress)
            Log.d("INITIALISE", "Wallet Address: $userWalletAddress")
            val blockhash = rpcClient.getLatestBlockhash(Commitment.CONFIRMED)
            val seeds = listOf("counter".encodeToByteArray())
            val counterAccountPDA =
                ProgramDerivedAddress.find(seeds, counterProgramId).getOrNull()!!
            val txInstruction = TransactionInstruction(
                counterProgramId,
                listOf(
                    AccountMeta(userWalletAddress, isSigner = true, isWritable = true),
                    AccountMeta(counterAccountPDA, isSigner = false, isWritable = true),
                    AccountMeta(SystemProgram.PROGRAM_ID, isSigner = false, isWritable = false)
                ),
                ByteArray(0)
            )
            val txMessage = Message.Builder()
                .addInstruction(txInstruction)
                .setRecentBlockhash(blockhash!!)
                .build()
            val unsignedTx = Transaction(txMessage)
            val serialisedUnsignedTx = unsignedTx.serialize()
            val result = walletAdapter.transact(activityResultSender) { authResult ->
                reauthorize(
                    identityUri = Uri.parse(IDENTITY_URI),
                    iconUri = Uri.parse(ICON_URI),
                    identityName = IDENTITY_APP_NAME,
                    authToken = _counterState.value.authToken
                )
                signAndSendTransactions(arrayOf(serialisedUnsignedTx))
            }

            handleTransactionResult(result)
            getCounterValue()
        } catch (e: Exception) {
            Log.e("FAILED_INITIALISATION", "Exception in initialising counter: ${e.message}")
        }
    }

This instruction will create a new Counter Account. I think I have done everything necessary when creating a Transaction. I have included the three accounts listed in the Account Context within the program; the signer, the PDA of the account being created and the System program. Is this the correct approach? Additionally, the mobile wallet is opened successfully but the transaction fails. What could I be doing wrong?

3
  • Does the transaction work if you just hard code a keypair and get an airdrop to it and use that to sign and send the transaction? Did you make sure you have devnet sol in your wallet on the phone to pay for the fee? Do you get any payload in the result from the transaction result?
    – Jonas H.
    Commented Nov 10 at 22:33
  • 1
    The wallet used to sign the transaction had enough SOL for the transaction. What I noticed my mistake was, is that I did not include the encoded instruction data when creating the TransactionInstruction. This instruction data includes the name of the instruction handler (function) within the deployed program.
    – Hansel
    Commented Nov 11 at 11:37
  • Ah great you figured it out \o/
    – Jonas H.
    Commented Nov 11 at 12:07

1 Answer 1

2

I finally found the solution to this problem.

The missing piece was adding the encoded instruction data to the TransactionInstruction.

Previously, I was passing an empty ByteArray as the instructionData argument.

val txInstruction = TransactionInstruction(
                counterProgramId,
                listOf(
                    AccountMeta(userWalletAddress, isSigner = true, isWritable = true),
                    AccountMeta(counterAccountPDA, isSigner = false, isWritable = true),
                    AccountMeta(SystemProgram.PROGRAM_ID, isSigner = false, isWritable = false)
                ),
                ByteArray(0)
            )

This now should be changed to include the instruction data

val instructionData = Borsh.encodeToByteArray(
                AnchorInstructionSerializer<Unit>("initialise_counter"),
                Unit
            )

val txInstruction = TransactionInstruction(
                counterProgramId,
                listOf(
                    AccountMeta(userWalletAddress, isSigner = true, isWritable = true),
                    AccountMeta(counterAccountPDA, isSigner = false, isWritable = true),
                    AccountMeta(SystemProgram.PROGRAM_ID, isSigner = false, isWritable = false)
                ),
                instructionData
            )

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.