LogoLogo
SupportGithubSign InGet Access
  • Introduction
  • GETTING STARTED
    • SDK Quick Start
    • API Quick Start
  • Guides
    • Web
      • Create a wallet
      • Send tokens
      • Sign a transaction
      • Simulate a transaction
      • Back up a wallet
      • Recover a wallet
      • Cross-device sessions
      • Manage wallet lifecycle states
      • Web authentication methods
      • Perform swaps
      • Add custom signature hooks
      • MPC progress callbacks
      • Portal API methods
      • Configure a custom subdomain
      • Eject a wallet
      • Using the EIP-1193 Provider
      • Legacy documentation
        • Back up a wallet
          • Backup Options
        • Recover a wallet
    • iOS
      • Create a wallet
      • Send tokens
      • Sign a transaction
      • Simulate a transaction
      • Back up a wallet
      • Recover a wallet
      • Cross-device sessions
      • Manage wallet lifecycle states
      • Connect with WalletConnect
      • Build a WebView
      • Perform swaps
      • Add custom signature hooks
      • MPC progress callbacks
      • Portal API methods
      • Manage ERC20 tokens
      • Eject a wallet
      • Legacy documentation
        • Back up a wallet
          • Backup Options
          • Passkey + Enclave Storage
        • Recover a wallet
      • Troubleshooting Tips
      • Feature Flags
    • Android
      • Create a wallet
      • Send tokens
      • Sign a transaction
      • Simulate a transaction
      • Back up a wallet
      • Recover a wallet
      • Cross-device sessions
      • Manage wallet lifecycle states
      • Connect with WalletConnect
      • Build a WebView
      • Perform swaps
      • Add custom signature hooks
      • MPC progress callbacks
      • Portal API methods
      • Eject a wallet
      • Legacy documentation
        • Back up a wallet
          • Backup Options
        • Recover a wallet
    • React Native
      • Create a wallet
      • Send tokens
      • Sign a transaction
      • Simulate a transaction
      • Back up a wallet
      • Recover a wallet
      • Cross-device sessions
      • Manage wallet lifecycle states
      • Connect with WalletConnect
      • Build a WebView
      • Perform swaps
      • Add custom signature hooks
      • MPC progress callbacks
      • Portal API methods
      • Eject a wallet
      • Legacy documentation
        • Back up a wallet
          • Backup Options
        • Recover a wallet
    • Enclave MPC API
      • Create a client
      • Create a wallet
      • Send tokens
      • Sign Ethereum transactions
      • Sign Solana transactions
      • Sign Tron transactions
      • Sign Stellar Transaction
      • Concurrent Transactions
      • Back up a wallet
      • Eject a wallet
  • Reference
    • iOS
      • createWallet
      • backupWallet
      • recoverWallet
      • ejectPrivateKeys
      • registerBackupMethod
      • setGDriveConfiguration
      • setPasskeyConfiguration
      • setPasskeyAuthenticationAnchor
      • setPassword
      • availableRecoveryMethods
      • doesWalletExist
      • isWalletBackedUp
      • isWalletOnDevice
      • isWalletRecoverable
      • getBalances
      • getAssets
      • getNftAssets
      • getTransactions
      • sendSol
      • evaluateTransaction
      • buildEip155Transaction
      • buildSolanaTransaction
      • getWalletCapabilities
    • Android
      • Reference Documentation
    • React Native
      • @portal-hq/core
      • Storage adapters
        • Cloud storage
          • @portal-hq/gdrive-storage
          • @portal-hq/icloud-storage
        • Mobile storage
          • @portal-hq/keychain
          • @portal-hq/mobile-key-values
    • Enclave MPC API
      • V1 endpoints
    • Client API
      • V3 endpoints
      • V1 endpoints
    • Custodian API
      • V3 endpoints
      • V1 endpoints
    • Swaps API
      • V3 endpoints
      • V1 endpoints
  • Resources
    • Flutter
      • iOS
      • Android
    • Error codes
      • Overview
      • MPC errors
      • Network errors
      • General errors
      • Encryption errors
      • Portal Connect errors
    • Portal's MPC architecture
    • Authentication and API Keys
    • Self-Managed Backups
    • Alert Webhooks
    • Wallet lifecycle
    • Backup options
      • Password/PIN
      • GDrive
      • iCloud
      • Passkey + Enclave
    • WalletConnect metadata
    • Advanced security scanning
    • Account abstraction
    • Security firewall
    • Eject
    • Security
    • Blockchain support
    • Chain ID formatting
    • Testnet faucets
    • Going to Production
    • Rate Limits
    • Multi-backup migration guide
    • Multi-wallet migration guides
      • Migrating from Android SDK v3.x.x to v4.x.x
      • Migrating from iOS SDK v3.0.x to v3.2.x
  • Support
    • Changelog
      • Android
      • iOS
      • React Native
      • Web
      • Past Releases
        • 2024 Releases
        • 2023 Releases
    • Celo Hackathon Hub
    • Glossary
Powered by GitBook
On this page
  • Estimating Gas
  • Signing Solana Transactions
  • Enabling the Enclave Signer

Was this helpful?

  1. Guides
  2. Android

Sign a transaction

Want to implement your own style, but with all the functionality Portal offers? Use these functions to implement your own custom web3 UI for your users.

PreviousSend tokensNextSimulate a transaction

Last updated 2 months ago

Was this helpful?

This example shows how the Portal Provider interacts with the MPC wallet and the blockchain.

The params have a hardcoded address for the transaction of sending 1 wei from our MPC wallet. The Provider then receives a signed transaction from our mobile MPC library and submits that to chain using the configured RPC url.

Here is a quick example of how you can make requests using Portal's web3 provider:

Ensure you have set the gateway URL correctly with or when you initialize the portal class.

// Imports...

class MainActivity : AppCompatActivity() {
    lateinit var portal: Portal

    private fun sendOneWei() {
        lifecycleScope.launch {
            val params = listOf(
              EthTransactionParam(
                from = address,
                to = toAddress,
                gas = "0x6000",
                gasPrice = null,
                maxFeePerGas = null,
                maxPriorityFeePerGas = null,
                value = "0x${BigInteger("1").toString(16)}",
                data = "",
              ),
            )
    
            Log.println(Log.INFO, "[PortalEx]", "Sending 1 wei: $params")
    
            try {
              Log.println(
                Log.INFO,
                "[PortalEx]",
                "Sending request for 'eth_SendTransaction' with params $params",
              )
    
              val result = portal.request(
                chainId = ethChainId,
                method = PortalRequestMethod.eth_sendTransaction,
                params = params
              )
    
              Log.println(Log.INFO, "[PortalEx]", "Transaction hash: ${result.result}")
              showTestResult(TestCase.Send1Wei, true, "Transaction hash: ${result.result}")
            } catch (err: Throwable) {
              Log.println(Log.WARN, "[PortalEx]", "Failed to send transaction: $err")
              showTestResult(TestCase.Send1Wei, false)
            } catch (e: Exception) {
              Log.println(Log.WARN, "[PortalEx]", "Failed to send transaction: $e")
              showTestResult(TestCase.Send1Wei, false)
            }
    }
}

Estimating Gas

By default, Portal will estimate and populate the gas property in a transaction object if the property is undefined.

To estimate the gas value manually use the eth_estimateGas RPC call and pass in your transaction as the parameter.

suspend fun estimateGas() {
    try {
        // Create the transaction params.
        val params = listOf(TransactionParams(
            "",
            "0x9AeCB4DA6b438830b88C5F40b6Bf36EF3073B350",
            "0x${BigInteger("1").toString(16)}",
            "",
            portal.address
        ))

        // Attempt to send the transaction.
        val response = portal.provider.request(
            chainId = "your chain Id"
            method = PortalRequestMethod.eth_estimateGas,
            params = params
        )

        Log.d("Portal", "Estimated gas: ${response.result}")
    } catch (err: Exception) {
        // ❌ Handle errors sending the transaction.
    }
}

Signing Solana Transactions

We offer a portal.sendSol function to make the process of sending sol very simple.

suspend fun sendSolana() {
    kotlin.runCatching {
      portal.sendSol(
        chainId = SOLANA_DEV_NET_CHAIN_ID,
        lamports = 1,
        to = SOLANA_TEST_ADDRESS,
      )
    }.onSuccess { response ->
      Timber.i("✅ Send Solana response: $response")
    }.onFailure {
      Timber.e("❌ Send Solana error: $it")
    }
}

We also offer methods to build your Solana (and Eth) transactions.

suspend fun buildSolanaTransaction() {
    val buildTransactionParam = BuildTransactionParam(
      to = SOLANA_TEST_ADDRESS,
      token = "SOL",
      amount = "0.001"
    )
    portal.api.buildSolanaTransaction(SOLANA_DEV_NET_CHAIN_ID, buildTransactionParam).onSuccess { response ->
      if (response.error == null) {
        Timber.i("✅ Build Transaction response: ${response.transaction}")
      } else {
        Timber.i("❌ Build Transaction response: ${response.error}")
      }
    }.onFailure {
      Timber.e("❌ Build Transaction error: $it")
    }
}
// In your settings.gradle
repositories {
    ...
    maven { url 'https://jitpack.io' }
}

// In your app/build.gradle
dependencies {
    ...
    implementation 'com.github.metaplex-foundation:SolanaKT:{version}'
}

Then build a Solana request and send it using portal.request(chainId, PortalRequestMethod.sol_signAndSendTransaction, listOf(solanaRequest)

// Here is how you can build the solana request using SolanaKt library
  suspend fun sendSolana(
    solanaChainId: String,
    fromAddress: String,
    toAddress: String,
    lamports: Long
  ): String {
    val recentBlockhashRpcResponse = portal.request(
      solanaChainId,
      PortalRequestMethod.sol_getLatestBlockhash,
      emptyList()
    ).result as PortalProviderRpcResponse
    val recentBlockhashResult = recentBlockhashRpcResponse.result
    val recentBlockhash = Gson().let {
      it.fromJson(it.toJson(recentBlockhashResult), SolGetLatestBlockhashResult::class.java)
    }

    val solanaRequest = prepareSolanaRequest(
      fromAddress,
      toAddress,
      lamports,
      recentBlockhash.value.blockhash
    )

    val transactionHash = portal.request(
      solanaChainId,
      PortalRequestMethod.sol_signAndSendTransaction,
      listOf(solanaRequest)
    ).result as String
    return transactionHash
  }

  suspend fun prepareSolanaRequest(
    fromAddress: String,
    toAddress: String,
    lamports: Long,
    recentBlockhash: String
  ): PortalSolanaRequest {
    val fromPublicKey = PublicKey(fromAddress)
    val toPublicKey = PublicKey(toAddress)

    val transferInstruction = SystemProgram.transfer(
      fromPublicKey,
      toPublicKey,
      lamports
    )

    Log.i("PortalSolana", "Transfer instruction: $transferInstruction")

    val transaction = Transaction()
    transaction.addInstruction(transferInstruction)
    transaction.recentBlockhash = recentBlockhash
    transaction.feePayer = fromPublicKey

    val message = transaction.compileMessage()

    Log.i("PortalSolana", "Compiled message: $message")

    val header = PortalSolanaHeader(
      numRequiredSignatures = message.header.numRequiredSignatures,
      numReadonlySignedAccounts = message.header.numReadonlySignedAccounts,
      numReadonlyUnsignedAccounts = message.header.numReadonlyUnsignedAccounts
    )

    val instructions = message.instructions.map { instruction ->
      PortalSolanaInstruction(
        instruction.programIdIndex,
        instruction.accounts,
        instruction.data
      )
    }

    val accountKeys = message.accountKeys.map { key -> key.toString() }

    return PortalSolanaRequest(
      message = PortalSolanaMessage(
        accountKeys = accountKeys,
        header = header,
        recentBlockhash = message.recentBlockhash,
        instructions = instructions
      )
    )
  }

Enabling the Enclave Signer

Executing MPC operations requires computation on the client device. Depending on the CPU of the client device this can take variable amounts of time, leading to inconsistent signing times across users.

To solve this, you can leverage the Enclave MPC API from your SDK to execute MPC operations server-side which leads to consistent (and often faster) signing speeds.

This feature leverages the Enclave MPC API by sending the user's key share to a Trusted Execution Environment (TEE) which runs the MPC code in a secure AWS Nitro Enclave with the same non-custodial guarantees as client-side MPC.

By enabling the useEnclaveMpcApi feature flag the client key share will be transmitted from the user device, but it is never stored.

TEEs in Nitro Enclaves work by encrypting memory and verifying execution. Encrypted memory means that all of the data being processed on the enclave can’t be accessed by anything other than the running application. Portal employees can’t even read the data on there! Verified execution means that a user can cryptographically verify that their request was handled in a secure enclave. When a user sends an API request to the enclave, Portal returns a set of signed “measurements” that can be verified by the enclave’s public key to ensure that the request was processed on an AWS Nitro Enclave.

Here’s how to enable it:

val portal = Portal(
    apiKey = "API_KEY_OR_SESSION_TOKEN",
    featureFlags = FeatureFlags(
        useEnclaveMpcApi = true
        // other feature flags...
    )
    // other configurations...
)

By setting useEnclaveMpcApi to true, the Portal instance will use the Enclave MPC API for signing transactions, ensuring faster computation and consistent performance across client devices.


And now you are signing transactions with Portal! 🙌 🚀 Next, we'll explore how to simulate a transaction so that you can create smoother experiences for your users.

If you would like to construct your own more advanced transaction for solana then here is how you can do it. Add to your project as it will help you build the transaction. (You can also use any other library of your choice.)

To learn more check out our blog post introducing.

Infura
Alchemy
SolanaKt library
the Enclave MPC API