Overview

Transaction Prompts give app the option to show users a capsule-managed prompt to manually approve or deny any transaction or message signing events.

This feature is required when interacting with wallets created outside your app, but can also be turned on for all wallets/transactions in your application. We recommend enabling this feature if you prefer not to implement transaction approval/display flows, or if you prefer users to explicitly approve every transaction on an embedded wallet.

How it Works

Transaction Prompts are mandatory in the following scenarios

  • Wallet Interaction: When a user attempts to sign a message or execute a transaction using their wallet in an application that is different from where the wallet was created.
  • Multi-Application Transactions: After a wallet has been used across multiple applications, any subsequent transactions will prompt the user for approval. This ensures that the user is aware of and consents to all interactions involving their wallet, regardless of the application being used.

To turn on Transaction Prompts for all transactions/wallets, enable “Always show transaction prompts” for your API key in the Developer Portal

User Interaction

When a transaction or message signing event is initiated, users will see a popup containing the following details:

  • Message Details: An encoded string representing the message.
  • Transaction Details:
    • From: The wallet address initiating the transaction.
    • To: The recipient wallet address.
    • Amount: The number of tokens being transferred.
    • Action: The specific action being performed (e.g., token transfer, minting an NFT).
    • Conversion Rates: Relevant exchange rates if applicable.
    • Chain Information: Information about the blockchain being used.

Technical Details

The feature is enabled by default when using the signMessage or signTransaction functions, either directly or through supported signer libraries (e.g., Ethers, Cosmos).

There is a default 30-second timeout for approvals. If this does not work for your use case, please reach out to the Capsule team for instructions on overriding this value.

Transaction Events and Statuses

  • On Approval: If the user approves the transaction or no approval is necessary, the signMessage/signTransaction function will return a SuccessfulSignatureRes result, which will contain the signature.
  • On Denial or Timeout: If the user denies the transaction or the timeout is reached, a TransactionReviewError will be thrown that includes the transactionReviewUrl that must be handled by the partner implementing the feature.

Error Handling

When implementing Transaction Prompts, various errors can arise during the signing process. It’s important to handle these errors gracefully to ensure a smooth user experience. Below are common scenarios and recommended handling strategies:

1. Transaction Denied

Description: The user denies the transaction or message signing request.

Error Handling:

try {
    await client.sendTokens(...);
} catch (error) {
    if (error instanceof TransactionReviewDenied) {
        console.warn("The transaction was denied by the user.");
    }
}

2. Timeout Reached

Description: The user does not respond to the popup within the configured timeout period. This returns an addition property of transactionReviewUrl and pendingTransactionId

  • pendingTransactionId - Can be used in conjunction with the getPendingTransaciton function available via the CoreCapsule class (or WebCapsule, by extension). If it does not exist, that means the user has denied the transaction request.
  • transactionReviewUrl - Can be used to open a popup if desired, which will present the user with the sign message / transaction popup.

Error Handling:

try {
    await client.sendTokens(...);
} catch (error) {
    if (error instanceof TransactionReviewTimeout) {
        console.warn("The transaction was denied by the user.");
    }
}

EVM Transaction Examples

Setting Up Ethers Signer

First, initialize the Ethers signer with your Capsule instance. This creates a signer that can be used for transactions on EVM networks.

import { CapsuleEthersSigner } from "@usecapsule/ethers-v6-integration";

// Initialize provider and signer
const provider = new ethers.EtherscanProvider(CHAIN_ID, API_KEY);

// Basic signer setup
const ethersSigner = new CapsuleEthersSigner(capsule, provider, WALLET_ID);

// Optional: Setup with custom timeout
const signerWithTimeout = new CapsuleEthersSigner(capsule, provider, WALLET_ID, TIMEOUT_MS);

Configuring EVM Transaction

Next, construct your transaction object. This defines all parameters needed for your transaction, such as the recipient address, amount, and gas settings.

// Prepare transaction parameters
const transactionConfig = {
  from: capsule.wallets?.[currentWalletId]?.address,
  to: txToAddress,
  value: web3.utils.toWei(txValue, "gwei"),
  gasLimit: txGasAmount,
  maxPriorityFeePerGas: txMaxPriorityFeePerGas,
  maxFeePerGas: txMaxFeePerGas,
  nonce,
  chainId,
  type: 2,
};

// For smart contract interactions
const smartContractConfig = {
  ...transactionConfig,
  smartContractAbi,
  smartContractFunctionName,
  smartContractFunctionArgs: smartContractFunctionArgs ? JSON.parse(smartContractFunctionArgs) : [],
  smartContractByteCode,
};

Sending EVM Transaction

Finally, send your transaction using the signer. Make sure to implement proper error handling for failed transactions.

try {
  const response = await ethersSigner.sendTransaction(transactionConfig);
  console.log("Transaction sent successfully:", response);
} catch (error) {
  console.error("Transaction failed:", error);
}

Cosmos Transaction Examples

Setting Up CosmJS Signer

First, create a CosmJS signer with your Capsule instance and connect it to a Stargate client. This setup enables interaction with Cosmos-based networks.

import Capsule from "@usecapsule/web-sdk";
import { CapsuleProtoSigner } from "@usecapsule/cosmjs-v0-integration";
import { SigningStargateClient } from "@cosmjs/stargate";

// Basic signer setup
const protoSigner = new CapsuleProtoSigner(capsule);

// Optional: Setup with custom timeout
const signerWithTimeout = new CapsuleProtoSigner(capsule, undefined, undefined, TIMEOUT_MS);

// Initialize client
const client = await SigningStargateClient.connectWithSigner(COSMOS_TESTNET_RPC, protoSigner);

Configuring Cosmos Transaction

Next, prepare your transaction configuration. For this example, we’ll set up a token transfer with the appropriate denominations and fees.

// Get sender address
const fromAddress = protoSigner.address;

// Configure token transfer
const transferConfig = {
  tokens: [
    {
      denom: "uatom",
      amount: "808",
    },
  ],
  fee: {
    amount: [
      {
        amount: "1000",
        denom: "uatom",
      },
    ],
    gas: "200000",
  },
};

Sending Cosmos Transaction

Finally, execute the transaction using the client. The example includes comprehensive error handling for common scenarios like user denial and timeouts.

try {
  const response = await client.sendTokens(
    fromAddress,
    COSMOS_DEFAULT_TO_ADDRESS,
    transferConfig.tokens,
    transferConfig.fee
  );
  console.log("Transaction sent successfully:", response);
} catch (error) {
  if (error instanceof TransactionReviewDenied) {
    console.log("Transaction review denied by user");
  } else if (error instanceof TransactionReviewTimeout) {
    console.log("Transaction review timed out");
  } else {
    console.error("Transaction failed:", error);
  }
}

Each example follows a three-step process: 1. Set up the signer 2. Configure the transaction 3. Execute the transaction with proper error handling