Transactions
WaaP makes it easy to send transactions across EVM and Sui chains. The SDK abstracts away cryptographic complexity and gas management, while remaining fully compatible with standard interfaces on each chain.
WaaP also supports asynchronous transactions (EVM) to enable smoother UX and sponsored transactions to remove UX frictions with gas fees.
Sending Transactions
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();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 be automatically managed via sponsored transactions (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 theuseWaapTransactionhook with callbacks.
How to enable async transactions?
There are 2 ways to do it:
-
All transactions async with
initWaaP -
Specific transactions async with
eth_sendTransaction
Global async transactions
Enable async mode for all transactions by passing asyncTxs: true when initializing WaaP:
import { initWaaP } from "@human.tech/waap-sdk";
initWaaP({
config: {
authenticationMethods: ["email", "phone", "social"],
allowedSocials: ["google", "twitter", "discord"],
styles: { darkMode: true },
},
asyncTxs: true, // resolve on the signed tx hash; broadcast continues in background
});With asyncTxs: true, every eth_sendTransaction behaves in async mode: the modal closes after user confirmation and your app receives broadcast progress via events or the useWaapTransaction hook.
asyncSigning: true is the deprecated v1 spelling. It still works, but it selects the old wire mode. See Legacy async mode.
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.
Signing is synchronous even in async mode. The promise waits for the signed artifact, so eth_sendTransaction resolves with the deterministic hash of the signed transaction. Only broadcast and confirmation continue in the background, and every lifecycle event is keyed by that same hash. So you do not need a correlation id.
personal_sign, eth_signTypedData_v4, and eth_signTransaction resolve with the signature directly and emit no events at all.
The deprecated asyncSigning: true wire mode behaves differently. It resolves with { pendingTxId, status: 'pending' }. See Legacy async mode.
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_complete → waap_tx_pending → waap_tx_confirmed or waap_tx_failed. Events fire only for eth_sendTransaction; other signing methods resolve synchronously and emit nothing.
| Event | When it fires | Payload |
|---|---|---|
waap_sign_complete | Signing finished, before broadcast | AsyncSignCompleteEvent |
waap_tx_pending | Transaction broadcast; waiting for confirmation | AsyncTxPendingEvent |
waap_tx_confirmed | Transaction confirmed on-chain | AsyncTxConfirmedEvent |
waap_tx_failed | Error during broadcast or confirmation | AsyncTxFailedEvent |
waap_sign_pending, waap_2fa_required and waap_sign_failed are no longer emitted in v2. Signing now completes before the promise resolves, so a signing failure rejects the promise instead. They remain exported for v1 consumers.
With asyncTxs: true or async: true, request() resolves with the signed transaction hash. Events carry that same hash, so no correlation id is needed. The deprecated asyncSigning mode instead returns { pendingTxId, status: 'pending' } (AsyncTxResponse).
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
| Feature | Sync Mode (default) | Async Mode |
|---|---|---|
| Modal behavior | Stays open until confirmed on-chain | Closes immediately after user confirms |
| User experience | User waits for confirmation | User can continue interacting |
| Progress updates | Modal shows progress | Events/hooks notify the app |
| Best for | Simple flows, critical transactions | Better UX, multiple transactions |
Legacy async mode (asyncSigning)
asyncSigning: true selects the v1 wire protocol. It still works, so existing
integrations keep running, but new code should use asyncTxs.
asyncTxs: true (current) | asyncSigning: true (v1) | |
|---|---|---|
eth_sendTransaction resolves with | the signed transaction hash | { pendingTxId, status: 'pending' } |
| Correlating events | by transaction hash | by pendingTxId |
waap_sign_pending / waap_2fa_required / waap_sign_failed | not emitted | emitted |
| Signing failure surfaces as | a rejected promise | a waap_sign_failed event |
The per-request async: true flag uses the current contract regardless of
which global option is set.
Sponsored 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.
See the Gas Tank guide for how to set up sponsorship for your project, and manage it in the Gas Tank Console.
Security and User Experience
- User Consent: All signing and transaction actions require explicit user approval via the WaaP modal.
- No Private Key Exposure: Signing keys are held inside a Trusted Execution Environment and never reach the browser or your dapp.
- Multi-Chain: The SDK handles chain switching and ensures transactions are sent to the correct network.
Related
- EVM Methods: Full SDK method reference including
request - Sui Methods: Sui Wallet Standard method reference
- Sign Messages: How to sign messages
- Supported EVM Chains: Available chains for signing operations
- Supported Sui Networks: Available Sui networks
- Blind Signing Resistance: How WaaP prevents blind signing