Skip to content

Customizing wallet prompts

By default, when you request a signature or transaction from a user's embedded wallet, Privy will display a UI to have your user preview the signature/transaction and confirm it.

These UIs are highly-customizable, allowing you to contextualize onchain actions within the broader experience of your product. Read below to learn more!

INFO

Only signatures and transactions requested from the embedded wallet allow for UI customization, as external wallet actions must be confirmed via the external wallet's UI, outside of the scope of Privy.

Signing messages

To request a signature from a user with a custom UI prompt, use the signMessage method returned by the usePrivy hook:

tsx
const {signMessage} = usePrivy();

When invoked, signMessage will request an EIP-191 personal_sign signature from the embedded wallet, and returns a Promise for the user's signature as a string.

signMessage takes the following parameters. In particular, you can use the optional uiConfig to customize the prompt shown to the user to contextualize their signing action.

ParameterTypeDescription
messagestringRequired. The message the user must sign as a string.
uiConfigSignMessageModalUIOptionsOptional. Options to customize the signature prompt shown to the user. Defaults to the values outlined below
uiConfig.titlestringOptional. The title text for the signature prompt. Defaults to 'Sign'.
uiConfig.descriptionstringOptional. The description text for the signature prompt. Defaults to 'Sign to continue'.
uiConfig.buttonTextstringOptional. The description text for the signature prompt. Defaults to 'Sign and Continue'.

Example

Below is an example of customizing the signature prompt:

tsx
import {usePrivy} from '@privy-io/react-auth';

function SignMessageButton() {
  const {signMessage} = usePrivy();
  const message = 'Hello world';
  const uiConfig = {
    title: 'Sample title text',
    description: 'Sample description text',
    buttonText: 'Sample button text',
  };

  return (
    <button
      onClick={async () => {
        // Use `signature` below however you'd like
        const signature = await signMessage(message, uiConfig);
      }}
    >
      Sign
    </button>
  );
}

Signing typed data

To have a user sign an EIP-712 typed data signature with a custom UI prompt, use the signTypedData method returned by the usePrivy hook:

tsx
const {signTypedData} = usePrivy();

When invoked, signTypedData will request an EIP-721 eth_signTypedData_v4 signature from the embedded wallet, and returns a Promise for the user's signature as a string.

signTypedData takes the following parameters. In particular, you can use the optional uiConfig to customize the prompt shown to the user to contextualize their signing action.

ParameterTypeDescription
typedDataSignedTypedDataParamsRequired. A JSON object that conforms to the EIP-712 TypedData JSON schema.
uiConfigSignMessageModalUIOptionsOptional. Options to customize the signature prompt shown to the user. Defaults to the values outlined below
uiConfig.titlestringOptional. The title text for the signature prompt. Defaults to 'Sign'.
uiConfig.descriptionstringOptional. The description text for the signature prompt. Defaults to 'Sign to continue'.
uiConfig.buttonTextstringOptional. The description text for the signature prompt. Defaults to 'Sign and Continue'.

Example

Below is an example of customizing the typedData prompt:

tsx
import {usePrivy} from '@privy-io/react-auth';

function SignTypedDataButton() {
  const {signTypedData} = usePrivy();

  // Example from https://github.com/MetaMask/test-dapp/blob/285ef74eec90dbbb4994eff4ece8c81ba4fc77f9/src/index.js#L1585
  const domain = {
    name: 'Example Message',
    version: '1.0.0',
    chainId: 1,
    salt: '0',
    verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC' as `0x${string}`,
  };

  // The named list of all type definitions
  const types = {
    Person: [
      {name: 'name', type: 'string'},
      {name: 'wallet', type: 'address'},
    ],
    Mail: [
      {name: 'from', type: 'Person'},
      {name: 'to', type: 'Person'},
      {name: 'contents', type: 'string'},
    ],
    // Necessary to define salt param type
    EIP712Domain: [
      {
        name: 'name',
        type: 'string',
      },
      {
        name: 'version',
        type: 'string',
      },
      {
        name: 'chainId',
        type: 'uint256',
      },
      {
        name: 'salt',
        type: 'string',
      },
      {
        name: 'verifyingContract',
        type: 'string',
      },
    ],
  };

  // The data to sign
  const value = {
    from: {
      name: 'Cow',
      wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
    },
    to: {
      name: 'Bob',
      wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
    },
    contents: 'Hello, Bob!',
  };

  const typedData = {primaryType: 'Mail', domain: domain, types: types, message: value};
  const uiConfig = {
    title: 'Sample title text',
    description: 'Sample description text',
    buttonText: 'Sample button text',
  };

  return (
    <button
      onClick={async () => {
        // Use `signature` below however you'd like
        const signature = await signTypedData(typedData, uiConfig);
      }}
    >
      Sign Typed Data
    </button>
  );
}

Transactions

To request a transaction from a user with a custom UI prompt, use the sendTransaction method returned by the usePrivy hook:

tsx
const {sendTransaction} = usePrivy();

When invoked, sendTransaction will make an eth_sendTransaction request to your user's wallet to send the specified transaction to the blockchain. The method returns a Promise for a TransactionReceipt object once it has been confirmed by the network.

INFO

Need to interact with a smart contract? Calling a smart contract method is a special case of a transaction.

sendTransaction takes the following parameters:

ParameterTypeDescription
requestDataUnsignedTransactionRequestRequired. The transaction request to be sent.
uiConfigSendTransactionModalUIOptionsOptional. Options to customize the transaction prompt shown to the user. Defaults to the values outlined below
fundWalletConfigFundWalletConfigOptional. Options to customize the fiat on-ramp flow for the wallet, if your app has Privy's fiat on-ramp enabled.

In particular, you should use:

  • UnsignedTransactionRequest to prepare the transaction to be sent
  • SendTransactionModalUIOptions to customize the transaction prompt shown to the user.

Building the transaction

To prepare the transaction for the user's embedded wallet, set your desired transaction details in the UnsignedTransactionRequest object.

The possible fields for this object are listed below. All of the fields are optional. If fields relating to gas consumption are left unspecified (e.g. gasPrice, maxFeePerGas), Privy will compute a real-time estimate for gas.

TIP

If you choose to manually configure any parameters related to gas fees, please make sure to pass in an explicit value for all of the following parameters: type (EIP-1559 or not), gasLimit, and

  • maxFeePerGas/maxPriorityFeePerGas for type: 2 transactions (EIP-1559), or
  • gasPrice for type: 0 or 1 transactions (legacy or EIP-2930).

If you only pass a subset of the parameters above, Privy will estimate the rest according to our defaults, which may result in unpredictable behavior for transactions.

FieldTypeDescription
tostringDestination address of the transaction. Must be hexadecimal formatted.
noncenumberNonce for this transaction. Defaults to the current transaction count for the wallet.
chainIdnumberChain ID for the network that this transaction should be sent on. Defaults to the current chain of the wallet.
datastringData to send to the receiving address, especially when calling smart contracts. Must be hexadecimal formatted.
valueQuantityAmount (in wei) to transfer from sender to receiver.
typenumberEIP-2718 type of this transaction.
gasLimitQuantityMax. units of gas that can be used by this transaction.
gasPriceQuantityPrice (in wei) per unit of gas for this transaction. This should only be set for non-EIP-1559 transactions.
maxFeePerGasQuantityMaximum price (in wei) per unit of gas to be paid total for this transaction. This includes the base fee and the priority fee. This should only be set for EIP-1559 transactions.
maxPriorityFeePerGasQuantityMaximum price (in wei) per unit of gas to be paid in addition to the base fee, to increase the transaction's priority. This includes the base fee and the priority fee. This should only be set for EIP-1559 transactions.

Customizing the prompt

To customize the transaction prompt for the user's embedded wallet, set your desired customizations in the SendTransactionModalUIOptions object.

The possible fields for this object are listed below. All of the fields are optional. If a default is not explicitly listed, Privy will omit that element from the UI if you do not set a value for that parameter. We recommend that you experiment with a number of prompt customizations to arrive at the best option for your users.

FieldTypeDescription
headerstringHeader text for the transaction screen. Defaults to 'Review transaction' or 'Send <token symbol>' if the transaction is a native token transfer.
descriptionstringDescription text for the transaction.
buttonTextstringText to show on the transaction confirmation button. Defaults to 'Submit'.
transactionInfoObjectInformation to show in the transaction details accordion. The fields for this object are outlined below.
transactionInfo.titleQuantityTitle for the transaction details accordion.
transactionInfo.actionnumberShort description of the action taken by the user. Should be <4 words (e.g. 'Send NFT'). Shown within the transaction details accordion.
transactionInfo.contractInfoObjectAdditional information to show in the transaction details accordion, when the transaction is a smart contract interaction.
transactionInfo.contractInfo.namestringName of the smart contract being called (e.g. 'Uniswap' or 'Seaport').
transactionInfo.contractInfo.urlstringURL to show for the smart contract.
transactionInfo.contractInfo.imgUrlstringURL of a hosted image to show in the transaction prompt.
transactionInfo.contractInfo.imgSize'sm' | 'lg'Size of hosted image to show, if transactionInfo.contractInfo.imgUrl is set.

Customizing price display

By default, Privy's transaction prompt will show prices in both the native token (e.g. ETH) and in USD. If you'd like to customize Privy's transaction prompt to show prices only in the native token and not in USD, update the config.embeddedWallets.priceDisplay property of your PrivyProvider component like so:

tsx
<PrivyProvider
  appId="insert-your-privy-app-id"
  config={{
    embeddedWallets: {
      priceDisplay: {

        primary: 'native-token', // Defaults to 'fiat-currency'
        secondary: null, // Defaults to 'native-token'
      },
    },
  }}
>
  {children}
</PrivyProvider>

Setting funding options

This section only applies to apps that have the MoonPay Fiat On-Ramp Plugin enabled.

If a user attempts to send a transaction with the embedded wallet and does not has sufficient funds to complete the transaction, Privy will automatically display an "Add funds" button in the transaction prompt for the user.

When a user clicks the "Add funds" button, they will be taken through a fiat on-ramp flow with MoonPay, where they can top up their wallet with the assets they need.

You can customize the fiat on-ramp flow for a given transaction by passing a FundWalletConfig object as a third, optional parameter to sendTransaction, like so:

tsx
// Configure the onramp to purchase ETH on Optimism
const fundWalletConfig = {currencyCode: 'ETH_OPTIMISM'};

await sendTransaction(insertYourTransaction, insertYourUIConfig, fundWalletConfig);

At minimum, we recommend configuring the currencyCode to ensure the user purchases the correct asset they need for their transaction.

Getting the receipt

Once invoked, sendTransaction will return a Promise for a TransactionReceipt object with details about the sent transaction. See a breakdown of this object's parameters below.

FieldTypeDescription
tostringReceiving address of the transaction. May either be an externally-owned account or a smart contract.
fromstringSending address of the transaction.
typenumberEIP-2718 type of this transaction.
status?number1 if the transaction was successful and 0 if the transaction reverted. Only available for transactions sent after the Byzantium Hard Fork.
blockHashstringHash of the block this transaction was included in.
transactionHashstringHash of this transaction. You may use this value to inspect the transaction on a Block Explorer for your respective network.
blockNumbernumberHeight/number of the block this transaction was included in.
gasUsedstringAmount of gas consumed by this transaction. Formatted as a hexadecimal string.
cumulativeGasUsedstringAmount of gas consumed by this transaction and every preceding transaction that was mined in the same block.
effectiveGasPricestringEffective gas price the transaction charged the sending address.
contractAddressstringIf the transaction deploys a smart contract, this is the address of the deployed contract. This should only happen if the receiving address (to) for this transaction is null.
logsArray<TransactionLog>List of all TransactionLogs emitted by this transaction.
logsBloomstringBloom filter of all addresses and topics included in any log in this transaction.
confirmationsnumberNumber of blocks that have been mined since this transaction, including the block that contains this transaction.
transactionIndexnumberThe index of this transaction in the list of transactions that were included in the transaction's block.
root?stringIntermediate state root of the transaction receipt. Only used by transactions sent before the Byzantium Hard Fork.
byzantiumbooleanReturns true if the transaction block was mined after the Byzantium Hard Fork.

Example

Below is an example of customizing the transaction prompt:

tsx
import {usePrivy} from '@privy-io/react-auth';
import type {UnsignedTransactionRequest, SendTransactionModalUIConfig} from '@privy-io/react-auth';

function SendTransactionButton() {
  const {sendTransaction} = usePrivy();

  const requestData: UnsignedTransactionRequest = {
    to: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
    chainId: 1,
    value: '0x3B9ACA00',
  };

  const uiConfig: SendTransactionModalUIConfig = {
    header: 'Sample header text',
    description: 'Sample description text',
    buttonText: 'Sample button text',
  };

  return (
    <button
      onClick={async () => {
        const txReceipt = await sendTransaction(unsignedTx, uiConfig);
        // The returned `txReceipt` has the type `TransactionReceipt`
      }}
    >
      Send ETH
    </button>
  );
}

Full UI Customization

Privy also enables your app to use your own, fully custom UIs to prompt users for signatures and transactions.

To do so, set the Wallet UIs toggle to off in the Embedded wallets page of the dashboard, per this guide.