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.

This example shows how the Portal Provider interacts with the blockchain.

The Provider has a address of our MPC wallet. The Provider then proxies the request for balance to the configured RPC url and returns the result.

Portal's web3 provider:

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

import PortalSwift

public var portal: Portal?

@IBAction func sendEip155Transaction(_: UIButton) {
  Task {
    do {
      // Ethereum Sepolia
      let chainId = "eip155:11155111" // CAIP-2 format.
  
      // Obtain the eip155 address of the user's wallet.
      guard let eip155Address = await portal.getAddress(chainId) else {
        throw PortalExampleAppError.addressNotFound()
      }
  
      // Create the transaction object.
      let transaction = [
        "from": eip155Address,
        "to": "0xRecipientAddress",
        "value": "0x10", // Native token amount to send (WEI for eip155:11155111).
      ]
  
      // Make the eth_sendTransaction request.
      let requestResponse = try await portal.request(
        chainId,
        withMethod: .eth_sendTransaction,
        andParams: [transaction]
      )
      
      // Obtain the transaction hash.
      guard let transactionHash = requestResponse.result as? String else {
        throw PortalExampleAppError.invalidResponseTypeForRequest()
      }
      
      // ✅ Great! You sent your first transaction successfully.
    } catch {
      // Handle any errors during the Portal provider request.
    }
  }
}

In the example above, we use .eth_sendTransaction. However, there's many other PortalRequestMethods available for you to use, which you can find in the SDK.

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, you'll want to use the eth_estimateGas RPC call and pass in your transaction as the parameter.

import PortalSwift

public var portal: Portal?

@IBAction func estimateGas(_: UIButton) {
  Task {
    do {
      // Ethereum Sepolia
      let chainId = "eip155:11155111" // CAIP-2 format.
      
      // Obtain the eip155 address of the user's wallet.
      guard let eip155Address = await portal.getAddress(chainId) else {
        throw PortalExampleAppError.addressNotFound()
      }
      
      // Create the transaction object.
      let transaction = [
        "from": eip155Address,
        "to": "0xRecipientAddress",
        "value": "0x10", // Native token amount to send (WEI for eip155:11155111).
      ]
  
      // Make the eth_estimateGas request with the transaction object.
      let requestResponse = try await portal.request(
        chainId,
        withMethod: .eth_estimateGas,
        andParams: [transaction]
      )
      
      // Retrieve the estimated gas value.
      guard let estimatedGas = requestResponse.result as? String else {
        throw PortalExampleAppError.invalidResponseTypeForRequest()
      }
      
      // ✅ Nice! You just retrieved the gas estimate.
    } catch {
      // Handle any errors during the Portal provider request.
    }
  }
}

Signing Solana Transactions

We offer a portal.sendSol function to make the process of sending sol very simple. If you would like to construct your own more advanced transaction for solana simply add Import SolanaSwift to the top of your file in order to start using this library (it is included in our sdk as a dependency)

Be sure to serialize the transaction with requiredAllSignatures: false. Then Base64 encode this object as a string and pass that in to the portal.request method as the only element in the array of the andParams argument.

...
Import SolanaSwift

func handleSolanaSendTrx() {
    Task {
      do {
        // devnet chainId
        let chainId = "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1"
        
        // Get the solana address
        guard let portal = self.portal, let fromAddress = await portal.getAddress(chainId) else {
          self.logger.error("ViewController.handleSolanaSendTrx() - ❌ Portal or address not initialized/found")
          self.stopLoading()
          return
        }
        
        // Format the solana address properly
        let fromPublicKey = try SolanaSwift.PublicKey(string: fromAddress)
        
        // Set the address you would like to send to
        let toPublicKey = try SolanaSwift.PublicKey(string: "2Xc2Z8jKVgSSQaGdCEbspReGbJkWGrEvR6aGmhSZAdjf")
        
        // Create the transfer instruction
        let transferInstruction = SystemProgram.transferInstruction(
          from: fromPublicKey,
          to: toPublicKey,
          lamports: 1
        )
        
        // Get the most recent blockhash
        let blockhashResponse: PortalProviderResult = try await portal.request(chainId, withMethod: .sol_getLatestBlockhash, andParams: [])
        guard let blockhashResResult = blockhashResponse.result as? SolGetLatestBlockhashResponse else {
          throw PortalSolError.failedToGetLatestBlockhash
        }
        
        // Initialize the transaction
        var transaction = SolanaSwift.Transaction(
          instructions: [transferInstruction],
          recentBlockhash: blockhashResResult.result.value.blockhash,
          feePayer: fromPublicKey
        )
        
        // Format the transaction
        let tx = try transaction.serialize(requiredAllSignatures: false)

        let txHash: PortalProviderResult = try await portal.request(chainId, withMethod: PortalRequestMethod.sol_signAndSendTransaction, andParams: [tx.base64EncodedString()])
        
        self.logger.info("ViewController.handleSolanaSendTrx() - ✅ Successfully signed message: SOL: \(txHash.result as! String)")
        
      } catch {
        self.logger.error("ViewController.handleSolanaSendTrx() - ❌ Generic error: \(error)")
      }
    }
  }

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.

Last updated