Skip to main content
Rainbow Wallet provides secure transaction and message signing for dApps connected via WalletConnect.

Request Types

Rainbow supports multiple types of signing requests:

Personal Sign

Sign plain text messages:
src/walletConnect/index.tsx
case RPCMethod.PersonalSign: {
  const [address, message] = params.sort(a => (isAddress(a) ? -1 : 1));
  const isHex = isHexString(message);

  let decodedMessage = message;
  try {
    if (isHex) {
      decodedMessage = toUtf8String(message);
    }
  } catch (err) {
    // Handle decode error
  }

  return {
    address: getAddress(address),
    message: decodedMessage,
  };
}
Use cases:
  • Login authentication
  • Message verification
  • Proof of ownership

Sign Typed Data

Sign structured data (EIP-712):
src/walletConnect/index.tsx
case RPCMethod.SignTypedData:
case RPCMethod.SignTypedDataV1:
case RPCMethod.SignTypedDataV3:
case RPCMethod.SignTypedDataV4: {
  const [address, message] = params;

  return {
    address: getAddress(address),
    message: JSON.parse(message),
  };
}
Typed data includes:
  • Domain separator
  • Message types
  • Structured values
  • Primary type
Common use cases:
  • Permit approvals (gasless ERC-20 approvals)
  • Meta-transactions
  • Order signing (Uniswap, OpenSea)
  • Vote delegation

Send Transaction

Send Ethereum transactions:
src/walletConnect/index.tsx
case RPCMethod.SendTransaction: {
  const [tx] = params;
  return {
    address: getAddress(tx.from),
  };
}
Transaction fields:
  • from: Sender address
  • to: Recipient address
  • value: ETH amount
  • data: Contract call data
  • gas: Gas limit
  • gasPrice or maxFeePerGas: Gas pricing
Rainbow does NOT support eth_sign for security reasons. This legacy method can be exploited to sign arbitrary transactions.

Request Handling

When a dApp sends a request:
src/walletConnect/index.tsx
export async function onSessionRequest(
  event: SignClientTypes.EventArguments['session_request']
) {
  setHasPendingDeeplinkPendingRedirect(true);
  const client = await getWalletKitClient();

  const { id, topic } = event;
  const { method: _method, params } = event.params.request;
  const method = _method as RPCMethod;

  if (method === RPCMethod.Sign) {
    // Reject eth_sign for security
    await client.respondSessionRequest({
      topic,
      response: formatJsonRpcError(id, `Rainbow does not support legacy eth_sign`),
    });
    showErrorSheet({
      title: 'Request Failed',
      body: 'eth_sign is not supported for security reasons.',
      sheetHeight: 270,
    });
    return;
  }
  
  if (isSupportedMethod(method)) {
    const { address, message } = parseRPCParams({ method, params });
    
    // Build request display
    const displayDetails = await getRequestDisplayDetails(
      event.params.request,
      nativeCurrency,
      chainId
    );
    
    // Show signing sheet
    handleWalletConnectRequest(request);
  }
}
1

Receive Request

dApp sends a signing or transaction request via WalletConnect.
2

Validate Request

Rainbow validates the request method and parameters.
  • Check if method is supported
  • Verify wallet ownership
  • Parse request parameters
  • Check for read-only wallet
3

Display Request

Show the request details in a signing sheet.
src/walletConnect/index.tsx
const request: WalletconnectRequestData = {
  clientId: session.topic,
  peerId: session.topic,
  requestId: event.id,
  dappName,
  dappUrl: peerMeta.url,
  displayDetails,
  imageUrl: maybeSignUri(dappImage, { w: 200 }),
  address,
  chainId,
  payload: event.params.request,
};
4

User Review

You review the request details and approve or reject.
5

Send Response

Rainbow sends the result back to the dApp.

Request Validation

Wallet Type Check

src/walletConnect/index.tsx
const selectedWallet = getWalletWithAccount(address);
const isReadOnly = selectedWallet?.type === WalletTypes.readOnly;

if (!selectedWallet || isReadOnly) {
  await client.respondSessionRequest({
    topic,
    response: formatJsonRpcError(id, `Wallet is read-only`),
  });
  
  showErrorSheet({
    title: 'Cannot Sign',
    body: 'This wallet is read-only and cannot sign transactions.',
    sheetHeight: 270,
  });
  return;
}
Validation checks:
  • Wallet exists
  • Wallet is not read-only
  • Address matches session
  • Chain is supported

Parameter Validation

src/walletConnect/index.tsx
if (!address || !message) {
  await client.respondSessionRequest({
    topic,
    response: formatJsonRpcError(id, `Invalid RPC params`),
  });
  
  showErrorSheet({
    title: 'Invalid Request',
    body: 'The request has invalid or missing parameters.',
    sheetHeight: 270,
  });
  return;
}

Signing Flow

Message Signing

1

Review Message

The message is displayed for your review.
  • Plain text messages are shown as-is
  • Hex messages are decoded to UTF-8 if possible
  • Typed data is formatted with structure
2

Verify dApp

Check the dApp name and URL to confirm authenticity.
3

Sign or Reject

Tap approve to sign, or reject to decline.
4

Biometric Confirmation (Optional)

Authenticate with Face ID/Touch ID if enabled.

Transaction Signing

1

Review Transaction

Transaction details are displayed:
  • To address
  • Value (ETH amount)
  • Contract interaction details
  • Decoded function call (if available)
  • Gas estimate
2

Check Gas Settings

Review and optionally adjust gas fees.
3

Approve or Reject

Confirm the transaction or reject it.
4

Sign & Broadcast

Rainbow signs and broadcasts the transaction to the network.

Response Handling

Successful Signing

src/walletConnect/index.tsx
export async function handleSessionRequestResponse(
  { sessionRequestEvent }: { sessionRequestEvent: SignClientTypes.EventArguments['session_request'] },
  { result, error }: { result: string | null; error: any }
) {
  const client = await getWalletKitClient();
  const { topic, id } = sessionRequestEvent;
  
  if (result) {
    const payload = {
      topic,
      response: formatJsonRpcResult(id, result),
    };
    await client.respondSessionRequest(payload);
  } else {
    const payload = {
      topic,
      response: formatJsonRpcError(id, error),
    };
    await client.respondSessionRequest(payload);
  }
  
  removeWalletConnectRequest({ walletConnectRequestId: sessionRequestEvent.id });
}
Success flow:
  1. User approves request
  2. Rainbow signs the message/transaction
  3. Result sent to dApp via WalletConnect
  4. Request removed from queue
  5. Success notification shown

Rejected Requests

Rejection flow:
  1. User rejects request
  2. Error response sent to dApp
  3. Request removed from queue
  4. User returned to previous screen

Hardware Wallet Support

Signing with hardware wallets (Ledger):
src/walletConnect/index.tsx
const authenticate: AuthRequestAuthenticateSignature = async ({ address }) => {
  const selectedWallet = getWalletWithAccount(address);
  const isHardwareWallet = selectedWallet?.type === WalletTypes.bluetooth;
  
  const signature = await (isHardwareWallet
    ? new Promise<string>((y, n) => {
        Navigation.handleAction(Routes.HARDWARE_WALLET_TX_NAVIGATOR, {
          async submit() {
            try {
              y(await loadWalletAndSignMessage());
            } catch (e) {
              n(e);
            }
          },
        });
      })
    : loadWalletAndSignMessage());
  
  return signature;
};
1

Detect Hardware Wallet

Rainbow detects if the connected wallet is a Ledger device.
2

Navigate to Ledger Flow

User is guided through the hardware wallet signing flow.
3

Approve on Device

User confirms the signature on the Ledger device.
4

Return Signature

Signature is returned and sent to the dApp.

Sign-In With Ethereum (SIWE)

Rainbow supports WalletConnect’s authentication feature:
src/walletConnect/sheets/AuthRequest.tsx
export function AuthRequest({
  requesterMeta,
  authenticate,
  verifiedData,
}: {
  requesterMeta: WalletKitTypes.SessionProposal['params']['proposer']['metadata'];
  authenticate: AuthRequestAuthenticateSignature;
  verifiedData?: Verify.Context['verified'];
}) {
  const auth = React.useCallback(async () => {
    const { success, reason } = await authenticate({ address });

    if (!success) {
      switch (reason) {
        case AuthRequestResponseErrorReason.ReadOnly:
          Alert({
            message: 'Please switch to a different wallet to sign this request',
            title: 'Cannot sign with this wallet',
          });
          break;
        default:
          Alert({
            title: 'Authentication Failed',
            message: 'An error occurred while authenticating.',
          });
      }
    } else {
      goBack();
    }
  }, [address, authenticate, goBack]);
}
SIWE use cases:
  • One-click dApp login
  • Gasless authentication
  • Session management
  • Proof of wallet ownership
SIWE provides a standardized way to authenticate using your Ethereum wallet, similar to “Sign in with Google”.

Request Queue

Multiple requests are queued:
src/state/walletConnectRequests.ts
export function addNewWalletConnectRequest({
  walletConnectRequest,
}: {
  walletConnectRequest: WalletconnectRequestData;
}): boolean {
  const state = getWalletConnectRequestsStore();
  
  // Check if request already exists
  if (state.requests.some(r => r.requestId === walletConnectRequest.requestId)) {
    return false;
  }
  
  // Add to queue
  state.requests.push(walletConnectRequest);
  return true;
}

export function removeWalletConnectRequest({
  walletConnectRequestId,
}: {
  walletConnectRequestId: number;
}) {
  const state = getWalletConnectRequestsStore();
  state.requests = state.requests.filter(
    r => r.requestId !== walletConnectRequestId
  );
}
Queue behavior:
  • Requests shown one at a time
  • FIFO (first in, first out) order
  • Each request must be approved or rejected
  • Queue persists across app restarts

Post-Request Redirect

After completing a request, redirect back to dApp:
src/walletConnect/index.tsx
const request: WalletconnectRequestData = {
  // ...
  walletConnectV2RequestValues: {
    sessionRequestEvent: event,
    address,
    chainId,
    onComplete(type: WalletconnectResultType) {
      if (IS_IOS) {
        Navigation.handleAction(Routes.WALLET_CONNECT_REDIRECT_SHEET, {
          type,
        });
      }
      maybeGoBackAndClearHasPendingRedirect({ delay: 300 });
    },
  },
};
Redirect types:
  • sign: Message signed
  • transaction: Transaction sent
  • reject: Request rejected
The redirect ensures you’re automatically returned to the dApp after signing, creating a seamless mobile experience.

Analytics

Signing events are tracked:
src/walletConnect/index.tsx
analytics.track(analytics.event.wcShowingSigningRequest, {
  dappName: request.dappName,
  dappUrl: request.dappUrl,
});

analytics.track(analytics.event.wcRequestFailed, {
  type: 'session_request',
  reason: 'read only wallet',
});
Tracked events:
  • Signing requests shown
  • Requests approved
  • Requests rejected
  • Request failures
  • Hardware wallet usage

Build docs developers (and LLMs) love