Send Transactions
Send gasless transactions and estimate fees.
This guide explains how to send a gasless transaction, estimate fees, sign, quote, and submit a UserOperation, cap transaction fees, reuse a recent quote, use parallel nonce lanes, and use a custom paymaster token.
Send a Gasless Transaction
You can send a transaction with gas fees paid in the paymaster token using account.sendTransaction():
const result = await account.sendTransaction({
to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
value: 1000000000000000n // 0.001 ETH in wei
})
console.log('UserOperation hash:', result.hash)
console.log('Fee paid in paymaster token:', result.fee)ERC-4337 transactions are gasless for the end user. Gas fees are paid through the configured paymaster using the specified paymaster token (e.g., USD₮).
Estimate Fees
You can estimate the fee for a transaction without broadcasting it using account.quoteSendTransaction():
const quote = await account.quoteSendTransaction({
to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
value: 1000000000000000n
})
console.log('Estimated fee:', quote.fee)Sign, Quote, and Submit a UserOperation
Use signTransaction() when a separate review or relay step needs a signed ERC-4337 v0.7 UserOperation. The method accepts one transaction, not a batch.
const tx = {
to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
value: 1000000000000000n
}
const signedUserOperation = await account.signTransaction(tx)
const quote = await account.quoteSendTransaction(signedUserOperation)
console.log('Signed operation fee ceiling:', quote.fee)
const result = await account.sendTransaction(signedUserOperation)
console.log('UserOperation hash:', result.hash)Only submit an operation produced by the same account with the intended fee-mode configuration. WDK preserves the signed nonce and configuration, does not rebuild or re-sign the operation, and does not reapply transactionMaxFee during signed submission. Do not mutate the operation, and submit it before its nonce becomes stale.
For signed UserOperations, sponsored mode quotes 0n. Other modes quote a 20% buffered native-gas ceiling in wei. In paymaster-token mode, that signed-operation quote is not a token-denominated paymaster charge.
Cap Transaction Fees
Set transactionMaxFee to reject non-sponsored operations newly built by sendTransaction() or signTransaction() when the estimated UserOperation fee is above your cap. A signed UserOperation passed to sendTransaction() is not checked against the cap again. Use transferMaxFee separately for token transfers.
const result = await account.sendTransaction({
to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
value: 1000000000000000n
}, {
transactionMaxFee: 100000
})transactionMaxFee applies to Paymaster Token and Native Coins modes. Sponsored operations return a zero fee to the caller and do not use this cap.
Reuse a Recent Quote
Default-lane, non-sponsored UserOperations built while quoting can be cached for up to 2 minutes. If you call sendTransaction() with the same transaction during that window, the account checks the current on-chain nonce before reusing a cached UserOperation. If the nonce has moved, it rebuilds before sending. Sponsored quotes do not cache a built UserOperation.
Quote methods do not resolve or reserve a nonce lane. A later send or sign using parallel or nonceKey rebuilds in that selected lane instead of reusing the quoted UserOperation.
Cache identity does not include per-call fee-mode configuration. Use the same fee-mode and paymaster settings for quoteSendTransaction() and the matching sendTransaction() or signTransaction(). Do not quote with one mode, paymaster token, or sponsorship policy and execute the same transaction with another while the quote is cached.
const tx = {
to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
value: 1000000000000000n
}
const quote = await account.quoteSendTransaction(tx)
console.log('Estimated fee:', quote.fee)
const result = await account.sendTransaction(tx)
console.log('UserOperation hash:', result.hash)Use Parallel Nonce Lanes
Beta.14 no longer reserves sequential nonces locally. By default, operations use ERC-4337 nonce key 0; two default-lane sends started before the first is included can select the same sequence and collide.
Before using nonzero lanes, deploy the Safe with one operation and wait for its UserOperation receipt. Your bundler must support parallel nonce keys, and its per-sender mempool limits still apply.
Use parallel: true to give each independent operation a fresh random lane:
const [first, second] = await Promise.all([
account.sendTransaction({ to: recipientA, value: 0n }, { parallel: true }),
account.sendTransaction({ to: recipientB, value: 0n }, { parallel: true })
])
console.log(first.hash, second.hash) // UserOperation hashesUse distinct named lanes for stable independent work streams. Strings are hashed as labels; they are not parsed as numeric keys.
const payroll = await account.sendTransaction(txA, { nonceKey: 'payroll' })
const refunds = await account.sendTransaction(txB, { nonceKey: 'refunds' })nonceKey takes precedence over parallel. A raw numeric key must fit uint192; use a bigint above Number.MAX_SAFE_INTEGER.
One lane is still sequential. Two concurrent sends using the same named or default lane can return UserOperation hashes even though one never receives a receipt. Wait for inclusion before reusing a lane. If calls depend on one another, batch them with sendTransaction([tx1, tx2]) so they execute in order under one nonce. Different lanes are independent and can be included in either order.
signTransaction() preserves the lane chosen by the same configuration rules. In beta.14, per-call parallel and nonceKey work at runtime but are missing from the published per-call TypeScript declarations; construction-level lane configuration is typed.
Send with Custom Paymaster Token
You can override the default paymaster token for a specific transaction by passing a config object to account.sendTransaction(). This example assumes the account is already in paymaster-token mode:
const result = await account.sendTransaction({
to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
value: 1000000000000000n
}, {
paymasterToken: {
address: '0x68749665FF8D2d112Fa859AA293F07A622782F38' // XAUT
}
})When switching from sponsored or native-coin mode, also set isSponsored: false and useNativeCoins: false, and provide any paymaster fields absent from the account configuration.
Next Steps
Learn how to transfer ERC-20 tokens.