Skip to Content
Guides (EVM)Transactions

Transactions

WaaP makes it easy to send transactions across all supported EVM-compatible blockchains. The SDK abstracts away cryptographic complexity and gas management, while remaining fully compatible with standard Ethereum interfaces.

WaaP also supports asynchronous transactions to enable smoother UX and sponsored transactions to remove UX frictions with gas fees.

Overview

  • Transaction Sending: Send EVM transactions (including contract calls and token transfers) with a unified interface.
  • Multi-Chain Support: Works seamlessly across all supported EVM chains, with automatic chain switching and gas management via the Gas Tank.

Usage

All transaction methods are available via the WaapProvider object returned by initWaaP() from the @human.tech/waap-sdk package.

import { initWaaP } from "@human.tech/waap-sdk"; initWaaP();

Sending Transactions

wallet_switchEthereumChain

Wallet’s current chain can be switched easily by calling wallet_switchEthereumChain.

Before calling eth_sendTransaction, the intended chain needs to be set.

Refer to https://chainlist.org/  for chain IDs.

// switch chain - get chainId at https://chainlist.org/ await window.waap.request({ method: "wallet_switchEthereumChain", params: [{ chainId: 11155111 }], // sepolia testnet });

Demo

eth_chainId

To get the wallet’s current chain ID, call eth_chainId. It returns chain ID in lowercase Hex.

// get current chainId // it returns chainId as lowercase Hex const chainId = await window.waap.request({ method: "eth_chainId" });

Demo

eth_sendTransaction

Send a transaction (ETH transfer, contract call, etc.) from the user’s WaaP wallet.

Check out the Async transactions section below.

// get address of the user const accounts = await window.waap.request({ method: "eth_requestAccounts", }); const address = accounts[0]; // switch chain - get chainId at https://chainlist.org/ await window.waap.request({ method: "wallet_switchEthereumChain", params: [{ chainId: 11155111 }], // sepolia testnet }); // get current chainId // it returns chainId as lowercase Hex const chainId = await window.waap.request({ method: "eth_chainId" }); // send transaction const txHash = await window.waap.request({ method: "eth_sendTransaction", params: [ { from: address, to: "0x...", // EOA or contract address value: "0x...", // hex-encoded value in wei data: "0x...", // optional, for contract calls gas: "0x...", // optional, estimated automatically if omitted // chainId is optional, defaults to current chain // chainId needs to be in lowercase hex: 0x1 // better to do wallet_switchEthereumChain and leave chainId blank here }, ], });
  • from: The user’s address (must match the logged-in WaaP wallet address).
  • to: The recipient address (EOA or contract).
  • value: Amount of ETH (in wei, hex-encoded).
  • data: (Optional) Contract call data.
  • gas: (Optional) Gas limit.
  • chainId: (Optional) EVM chain ID in lowercase Hex.

Refer to https://chainlist.org/  for chain IDs.

Features:

  • WaaP will prompt the user to review and approve the transaction.
  • Works across all supported chains.
  • Gas fees can automatically managed via the Gas Tank (no need to hold native tokens).

Demo


Async Transactions

Async Transaction signing allows the wallet modal to close immediately after user’s confirmation. Transaction signing and broadcasting happen in the background, and your app is notified of progress via events or the useWaapTransaction hook throughout the transaction lifecycle.

Benefits

  • Modal closes on confirm — User confirms in the modal, then the modal closes; no need to keep it open until the transaction is completed.
  • Signing in background — Signing and broadcast happen after the modal is closed.
  • Events and hooks notify the app — Subscribe via window.waap.on() to get progress updates or use the useWaapTransaction hook with callbacks.

How to enable async transactions?

There are 2 ways to do it:

  1. All transactions async with initWaaP

  2. Specific transactions async with eth_sendTransaction

Global async transactions

Enable async mode for all transactions by passing asyncSigning: true when initializing WaaP:

import { initWaaP } from "@human.tech/waap-sdk"; initWaaP({ config: { authenticationMethods: ["email", "phone", "social"], allowedSocials: ["google", "twitter", "discord"], styles: { darkMode: true }, }, useStaging: false, asyncSigning: true, // modal closes on confirm; signing and broadcast happen in background });

With asyncSigning: true, every eth_sendTransaction behaves in async mode (modal closes after user confirmation; your app receives progress via events or the useWaapTransaction hook).

Specific async transaction

Enable async mode for specific transactions by passing async: true when requesting eth_sendTransaction

// send transaction in async mode // Note: request resolves with { pendingTxId, status: 'pending' }, not txHash. // Get txHash from events (waap_tx_pending, waap_tx_confirmed) or useWaapTransaction callbacks. const { pendingTxId, status } = await window.waap.request({ method: "eth_sendTransaction", params: [ { from: address, to: "0x...", // EOA or contract address value: "0x...", // hex-encoded value in wei data: "0x...", // optional, for contract calls }, ], async: true, // enable async mode });

The modal will close immediately after user confirmation, and the transaction will be signed and broadcast in the background.

When a transaction is sent in async mode (asyncSigning: true or async: true), the request promise does not resolve with the transaction hash. It resolves immediately with { pendingTxId, status: 'pending' }. The txHash is only available later via the Event System (e.g. waap_tx_pending or waap_tx_confirmed) or the useWaapTransaction hook callbacks. Use pendingTxId to correlate the async response with those events or callbacks.

Event System

When using async mode, the wallet modal closes immediately after user confirmation; signing and (for eth_sendTransaction) broadcast happen in the background. WaaP emits events via window.waap.on() so your app can track progress. You can also use the useWaapTransaction hook for a callback-based API.

Event lifecycle: waap_sign_pending → (optionally waap_2fa_required) → waap_sign_complete or waap_sign_failed → for send transaction: waap_tx_pendingwaap_tx_confirmed or waap_tx_failed.

EventWhen it firesPayload
waap_sign_pendingAfter user confirms in the modal; async signing has startedAsyncSignPendingEvent
waap_2fa_requiredHigh-risk transaction requires 2FA; modal re-opens for 2FAAsync2faRequiredEvent
waap_sign_completeSigning finished (before broadcast for transactions)AsyncSignCompleteEvent
waap_sign_failedError during signingAsyncSignFailedEvent
waap_tx_pendingTransaction broadcast; waiting for confirmation (only for eth_sendTransaction)AsyncTxPendingEvent
waap_tx_confirmedTransaction confirmed on-chain (only for eth_sendTransaction)AsyncTxConfirmedEvent
waap_tx_failedError during broadcast or confirmationAsyncTxFailedEvent

When asyncSigning: true or async: true, request() returns immediately with { pendingTxId, status: 'pending' } (AsyncTxResponse). Use pendingTxId to correlate events for the same operation.

Payload types:

  • AsyncSignPendingEvent{ pendingTxId: string, txRequest: { to?, from?, value?, data?, chainId? } }
  • Async2faRequiredEvent{ pendingTxId: string }
  • AsyncSignCompleteEvent{ pendingTxId: string, signature: string, serializedTx: string | null } (null for message signing)
  • AsyncSignFailedEvent{ pendingTxId: string, error: string }
  • AsyncTxPendingEvent{ pendingTxId: string, txHash: string }
  • AsyncTxConfirmedEvent{ pendingTxId: string, txHash: string, receipt: { blockNumber, blockHash, transactionHash, status: 'success' \| 'reverted', gasUsed } }
  • AsyncTxFailedEvent{ pendingTxId: string, error: string, stage: 'broadcast' \| 'confirmation' }
// Subscribe to async transaction events window.waap.on("waap_sign_pending", (data: { pendingTxId: string; txRequest: object }) => { console.log("Signing started", data.pendingTxId); }); window.waap.on("waap_2fa_required", (data: { pendingTxId: string }) => { console.log("2FA required for high-risk transaction", data.pendingTxId); }); window.waap.on("waap_sign_complete", (data: { pendingTxId: string; signature: string; serializedTx: string | null }) => { console.log("Signing complete", data.pendingTxId); }); window.waap.on("waap_sign_failed", (data: { pendingTxId: string; error: string }) => { console.error("Signing failed", data.pendingTxId, data.error); }); window.waap.on("waap_tx_pending", (data: { pendingTxId: string; txHash: string }) => { console.log("Transaction broadcast", data.txHash); }); window.waap.on("waap_tx_confirmed", (data: { pendingTxId: string; txHash: string; receipt: object }) => { console.log("Transaction confirmed", data.txHash, data.receipt); }); window.waap.on("waap_tx_failed", (data: { pendingTxId: string; error: string; stage: string }) => { console.error("Transaction failed", data.stage, data.error); });

useWaapTransaction Hook

For React applications, the useWaapTransaction hook provides a convenient way to manage async transactions with callbacks:

import { useWaapTransaction } from "@human.tech/waap-sdk"; function TransactionComponent() { const { sendTransaction, status, txHash, error } = useWaapTransaction({ onPending: () => { console.log("Transaction pending..."); }, onSigned: (hash) => { console.log("Transaction signed:", hash); }, onConfirmed: (hash) => { console.log("Transaction confirmed:", hash); }, onFailed: (error) => { console.error("Transaction failed:", error); }, on2FARequired: () => { console.log("2FA required"); }, }); const handleSend = async () => { await sendTransaction({ from: address, to: "0x...", value: "0x...", }); }; return ( <div> <button onClick={handleSend}>Send Transaction</button> <p>Status: {status}</p> {txHash && <p>Hash: {txHash}</p>} {error && <p>Error: {error.message}</p>} </div> ); }

Sync vs Async Comparison

FeatureSync Mode (default)Async Mode
Modal behaviorStays open until confirmed on-chainCloses immediately after user confirms
User experienceUser waits for confirmationUser can continue interacting
Progress updatesModal shows progressEvents/hooks notify the app
Best forSimple flows, critical transactionsBetter UX, multiple transactions

WaaP supports sponsoring transactions across all supported chains for seamless user experience. Gas sponsorship can be configured to a specific contract or a specific contract method.

Please refer to the Gas Tank guide to see how to configure the gas tank for your project.


Security and User Experience

  • User Consent: All signing and transaction actions require explicit user approval via the WaaP modal.
  • No Private Key Exposure: The private key is never reconstructed anywhere but done via 2PC (Two-party computation) and 2PC-MPC (Two-party computation - Multi-party computation).
  • Multi-Chain: The SDK handles chain switching and ensures transactions are sent to the correct network.
Last updated on