Send Transactions
Quote, sign, and send paymaster-funded Solana transactions.
This guide explains how to send native SOL, track transaction finality, sign without broadcasting, quote paymaster fees, send an already signed transaction, and use prebuilt transaction messages.
Use BigInt values for token and lamport inputs. Separately, keep paymaster payment amounts at or below Number.MAX_SAFE_INTEGER: beta.6 still converts the unsigned-flow u64 payment through Number, so larger fee quotes, cap comparisons, and returned fees can round despite bigint inputs.
Send Native SOL
Use sendTransaction({ to, value }) to send native SOL through the configured paymaster:
const result = await account.sendTransaction({
to: 'Recipient1111111111111111111111111111111',
value: 1000000n
})
console.log('Transaction hash:', result.hash)
console.log('Paymaster fee:', result.fee)The returned fee is denominated in the configured paymaster token's base units, not lamports.
Wait for Finality
Pass the returned Solana signature to waitForTransaction():
const receipt = await account.waitForTransaction(result.hash, {
target: 'final'
})
console.log('Finalized in slot:', receipt.block)
if (receipt.success === false) {
console.log('The transaction was finalized but execution failed')
}processed, confirmed, and finalized map to WDK pending, confirmed, and final. The default poll interval is four seconds and timeout is 60 seconds. The module does not currently emit dropped; a never-landed or evicted signature times out. Inspect success after settlement.
Quote Before Sending
Use quoteSendTransaction() to get the paymaster fee before submitting:
const quote = await account.quoteSendTransaction({
to: 'Recipient1111111111111111111111111111111',
value: 1000000n
})
console.log('Paymaster fee estimate:', quote.fee)Set a Transaction Fee Cap
Set transactionMaxFee in the wallet config or as a per-call override to cancel sendTransaction() or signTransaction() when the paymaster fee is higher than your cap:
const result = await account.sendTransaction({
to: 'Recipient1111111111111111111111111111111',
value: 1000000n
}, {
transactionMaxFee: 500000n
})quoteSendTransaction() returns the estimated fee without enforcing transactionMaxFee. sendTransaction() and signTransaction() reject only when the fee is greater than the cap, so a fee equal to the cap is allowed.
Sign Without Broadcasting
Use signTransaction() when another process will inspect or submit the fully signed transaction:
const signedTransaction = await account.signTransaction({
to: 'Recipient1111111111111111111111111111111',
value: 1000000n
}, {
transactionMaxFee: 500000n
})Treat the fully signed transaction as a sensitive, broadcastable authorization payload. Do not log it.
The module adds the payment instruction, checks transactionMaxFee, signs with the owner account, asks the paymaster to sign, and returns a FullySignedTransaction. It does not broadcast from signTransaction().
Quote and Send a Signed Transaction
In 1.0.0-beta.6, owned accounts accept the FullySignedTransaction returned by signTransaction() in both quoteSendTransaction() and sendTransaction():
const signedTransaction = await account.signTransaction({
to: 'Recipient1111111111111111111111111111111',
value: 1000000n
}, {
transactionMaxFee: 500000n
})
const { fee } = await account.quoteSendTransaction(signedTransaction)
console.log('Embedded paymaster fee:', fee)
const result = await account.sendTransaction(signedTransaction, {
transactionMaxFee: 500000n
})
console.log('Transaction hash:', result.hash)For the exact signed output produced above, quoteSendTransaction() does not request a new paymaster quote or broadcast anything. It searches for an SPL payment to the configured paymaster's associated token account for the effective paymasterToken, including a per-call override. It decodes the fee as an exact bigint in that token's base units. A missing match raises NoSuchElementError; it no longer returns zero.
sendTransaction(signedTransaction) searches for that same fee, enforces transactionMaxFee, base64-encodes the fully signed wire transaction, and submits it through the configured Solana RPC with encoding: 'base64'. It does not call the paymaster's signAndSendTransaction() again.
Missing-payment rejection is not a general signed-transaction validator. Accept the exact output of this account's signTransaction() with the same fee-token configuration. For any other source, independently decode and verify the intended instructions, payment token and destination, fee payer, transaction lifetime, and account and paymaster signatures before calling sendTransaction().
A signed transaction is immutable. This flow does not obtain a fresh blockhash, durable nonce, payment instruction, or paymaster signature. Submit the exact signed payload while its existing lifetime is valid, and check its original signature before retrying after an uncertain RPC result.
Use a TransactionMessage
Pass a prebuilt TransactionMessage when your app needs custom instructions.
const quote = await account.quoteSendTransaction(transactionMessage)
console.log('Paymaster fee estimate:', quote.fee)
const result = await account.sendTransaction(transactionMessage)
console.log('Transaction hash:', result.hash)If the message already includes a recent blockhash or durable nonce lifetime, the module preserves it. If it does not, the module adds a blockhash lifetime before it requests payment instructions and signatures. A signed result keeps that lifetime; sendTransaction(signedTransaction) does not refresh it.
If a prebuilt message sets feePayer, it must equal paymasterAddress. The module sets the fee payer to the paymaster address before asking the paymaster for fee instructions.
Read Transaction Status
Use getTransaction(hash) for normalized status without waiting:
const receipt = await account.getTransaction(result.hash)
console.log('Finality:', receipt.finality)
console.log('Confirmations:', receipt.confirmations)getTransactionReceipt() remains available for the native transaction object but is deprecated.
Override Paymaster Token
You can override the paymaster token for one quote, sign, or send call. Keep the token and cap in one reviewed override, quote first, and require explicit confirmation of the fee token and amount before sending:
const tx = {
to: 'Recipient1111111111111111111111111111111',
value: 1000000n
}
const feeConfig = {
paymasterToken: {
address: 'AlternateFeeMint11111111111111111111111111'
},
transactionMaxFee: 500000n
}
const quote = await account.quoteSendTransaction(tx, feeConfig)
await confirmFeeTokenAndAmount(feeConfig.paymasterToken.address, quote.fee)
const result = await account.sendTransaction(tx, feeConfig)confirmFeeTokenAndAmount() represents an application-owned confirmation boundary. The quote itself does not enforce the cap; sendTransaction() does.
For an already signed transaction, the payment token is embedded in the signed message. Retain the same feeConfig when signing, quoting the signed result, and broadcasting. Its paymasterToken tells the module which payment to decode; it cannot change the signed payment. Undefined override fields retain configured defaults.
You can use the same fee-token configuration throughout the signed flow:
const signedTransaction = await account.signTransaction(tx, feeConfig)
const signedQuote = await account.quoteSendTransaction(signedTransaction, feeConfig)
console.log('Embedded fee:', signedQuote.fee)
const result = await account.sendTransaction(signedTransaction, feeConfig)
console.log('Transaction hash:', result.hash)Next Steps
To transfer SPL tokens instead of native SOL, see Transfer SPL Tokens.